洛谷 P4322 [JSOI2016]最佳团体&&P1642 规划


链接

P4322

P1642

分析

都是在树上选出一个连通块,P4322 是必须包括树根,P1642 只需联通,使得 \(\frac{\sum a}{\sum b}\) 最大,考虑01分数规划。

二分答案,然后现在需要知道在树中选 \(k\) 个点的最大 \(a_i-mid\times b_i\) 之和,考虑树上的背包 dp,设 \(f[u][k]\) 表示在 \(u\) 这棵字数内选 \(k\) 个点的最大值。
树上背包是 \(n^2\) 的,注意不能写错了,应该是先枚举合并当前子树后的总大小 \(i\),再枚举当前儿子的大小 \(j\)\(f[u][i]=\max\{f[u][i],f[u][i-j]+f[v][j]\}\)
引用一段时间复杂度的证明:
因为每次合并一棵子树时付出的代价是已经合并的兄弟子树的大小之和正在合并的这棵子树的大小,实质上是树上每对节点在 LCA 处贡献时间复杂度,所以是 \(n^2\) 的。

于是我们可以在 \(O(n^2)\) 的时间内得到 \(f\),于是根据 \(f[0][k]\)(注意 P4322 树根是0) 的正负继续二分答案。可以在总时间复杂度为 \(O(n^2\log n)\) 的时间内解决P4322,稍微有些卡常。

然后再看 P1642,不选到树根的情况怎么办呢?一个 NAIVE 的想法是枚举树根 \(O(n^3\log n)\)。但是我们考虑以 1 为根的情况下,发现对于任意一个连通块内所有点,一定都在连通块深度最浅的点的子树内。所以只要把每个点的 \(f[u][k]\) 都判断一遍,一定可以判断完每个连通块。
所以我们仍只需要以 1 为根做一次 01 分数规划,用 \(f[i][k]\ge 0(\exists 1\le i\le n)\) 来继续二分。


P4322code

点击查看代码
#include
using namespace std;
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
const int N=2505;
const double eps=1e-5;
struct edge{int v,nxt;}e[N<<1];
int head[N],en;
inline void insert(int u,int v){
	e[++en].v=v;
	e[en].nxt=head[u];
	head[u]=en;
}
int n,m,siz[N],S;
double s[N],t[N],l,r,mid,f[N][N];
inline void dfs1(int u,int fa){
	for(register int i=2;i<=m;i++)f[u][i]=-1000000000;
	f[u][1]=s[u]-mid*t[u],siz[u]=1; 
	for(int k=head[u],v=e[k].v;k;k=e[k].nxt,v=e[k].v)
		if(v^fa){
			dfs1(v,u),siz[u]+=siz[v];
			for(register int i=min(m,siz[u]);i>=1;i--)
				for(register int j=min(i-1,siz[v]);j>=1;j--)
					f[u][i]=max(f[u][i],f[u][i-j]+f[v][j]);		
		}
}
signed main(){
	m=in+1,n=in;
	for(register int i=1,fa;i<=n;i++){
		t[i]=in,s[i]=in,fa=in,S+=s[i];
		insert(fa,i),insert(i,fa);		
	}
	l=0,r=S;
	while(r-l>eps){
		mid=(l+r)/2,dfs1(0,0);
		if(f[0][m]>=0)l=mid;
		else r=mid;
	}
	cout<

P1642code

点击查看代码
#include
using namespace std;
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
const int N=105;
const double eps=1e-4;
struct edge{int v,nxt;}e[N<<1];
int head[N],en;
inline void insert(int u,int v){
	e[++en].v=v;
	e[en].nxt=head[u];
	head[u]=en;
}
int n,m,siz[N],S;
double s[N],t[N],l,r,mid,f[N][N];
inline void dfs1(int u,int fa){
	for(register int i=2;i<=m;i++)f[u][i]=-1000000000;
	f[u][1]=s[u]-mid*t[u],siz[u]=1; 
	for(int k=head[u],v=e[k].v;k;k=e[k].nxt,v=e[k].v)
		if(v^fa){
			dfs1(v,u),siz[u]+=siz[v];
			for(register int i=min(m,siz[u]);i>=1;i--)
				for(register int j=min(i-1,siz[v]);j>=1;j--)
					f[u][i]=max(f[u][i],f[u][i-j]+f[v][j]);		
		}
}
signed main(){
	n=in,m=n-in;
	for(int i=1;i<=n;i++)s[i]=in,S+=s[i];
	for(int i=1;i<=n;i++)t[i]=in;
	for(int i=1,u,v;ieps){
		bool flag=0;
		mid=(l+r)/2,dfs1(1,0);
		for(int i=1;i<=n;i++)
			flag|=(f[i][m]>=0);
		if(flag)l=mid;
		else r=mid;
	}
	cout<