P4322 [JSOI2016] 最佳团体

Link\text{Link}

看到题解区里都是大片大片的树形 DP\text{DP},这里给出一个不用琢磨复杂度是 O(n2)O(n^2) 还是 O(n3)O(n^3)dfs\text{dfs} 序做法。

题意

给定一个 n+1n+1 个节点的以 00 为根的树,树上的每个节点都有其价值 PiP_i 和费用 SiS_i。在树上选择 k+1k+1 个节点,在满足任意一个节点如果被选择了,那么其父亲也一定要被选择的条件下,求性价比最高的一个选点方案。

分析

求性价比最高有一个经典套路:0101 分数规划,这部分其他题解讲的已经很清楚了,这篇题解讲着重讲解 check\text{check} 的部分,即如何选出 k+1k+1 个点使其点权和最大。

显然,选完 k+1k+1 个点后,选择的点就构成了另一棵树,而所有未选择的点构成了一片森林。选择的点构成的树的形态是变化多样的,而未选择的点构成的那些树却遵循原树的性质,正难则反,于是我们可以把问题转化为在符合题意的前提下从 n+1n+1 个点中剔除 nkn-k 个节点,使剔除的点权和最小的情况下的点权和。

每次剔除点,我们剔除的连通块必然是一棵树,所以只要知道其根节点即可,考虑用 dfs\text{dfs} 序来转化问题,在 dfs\text{dfs} 进入某个节点的时候记录次该节点,退出时也记录一次,将两次的 dfs\text{dfs} 序分别记为 stx,edxst_x,ed_x,这样一个节点对应的区间就是以该节点为根的子树,同时可以发现,剔除的两棵子树所对应的区间是没有相交部分的,因此树上的问题就可以转化为序列上的选择区间问题。

szisz_i 表示以节点 ii 为根的子树的大小, reire_i 表示 edx=ied_x=i 的节点的编号,若不存在则为 00,再设 fu,jf_{u,j} 表示最后一次剔除的子树根节点为 uu,总共剔除了 jj 个节点的最小点权和,那么有:

fu,j=minedv<stu{fv,jszu}+12i=stueduwreif_{u,j}=\min_{ed_v< st_{u}}\{f_{v,j-sz_u}\}+\frac{1}{2}\sum_{i=st_u}^{ed_u}w_{re_i}

转移是按照 edued_u 从小到大转移的,wiw_i 那部分可以直接用前缀和处理,但是我们直接枚举 vv 的话复杂度仍然高达 O(n3)O(n^3),于是考虑优化,发现 min\min 那部分的第二维都一样,于是可以开一个数组 sjs_j 记录 min{fv,j}\min\{f_{v,j}\},但不满足 min\min 的前置条件,由于 nn 很小,所以我们干脆再开一维,记 si,js_{i,j}minedv<=i{fv,jszu}\min_{ed_v<=i}\{f_{v,j-sz_u}\},那么就有:

fu,j=sstu,jszu+12i=stueduwreif_{u,j}=s_{st_u,j-sz_u}+\frac{1}{2}\sum_{i=st_u}^{ed_u}w_{re_i} si,j={min{si1,j,frei,j}rei>0si1,jrei=0s_{i,j}=\begin{cases}\min\{s_{i-1,j},f_{re_i,j}\}&re_i>0\\s_{i-1,j}&re_i=0\end{cases}

于是最终时间复杂度降到 O(n2)O(n^2),但本人代码由于常数过大,要开 O2\text{O2} 才能过。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
	long long x=0,f=1;char ch=getchar();
	while(!isdigit(ch))
	{if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=2505;
int k,n;
int head[N],ver[N],nxt[N],tot;
int c[N],v[N];
int st[N],ed[N],num,re[N<<1],sz[N];
double w[N],f[N][N],sum,s[N<<1][N],d[N<<1];
void add(int x,int y){
	ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;
}
void dfs(int x){
	sz[x]=1;
	st[x]=++num;
	for(register int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		dfs(y);sz[x]+=sz[y];
	}
	ed[x]=++num;re[num]=x;
}
bool check(double x){
	sum=0;
	for(register int i=1;i<=n;i++){
		w[i]=v[i]-c[i]*x;
		d[st[i]]=d[ed[i]]=w[i]/2.0;
		sum+=w[i];
		s[0][i]=1e9;
		for(register int j=1;j<=n-k;j++)
			f[i][j]=1e9;
	}
	for(register int i=1;i<=2*n;i++)
		d[i]+=d[i-1];
	s[0][0]=0;
	double ans=1e9;
	for(register int i=1,x;i<=2*n;i++){
		for(register int j=0;j<=n-k;j++)
			s[i][j]=s[i-1][j];
		if(re[i]){//0是不会被踢除的,当然实际上只是作者懒
			x=re[i];
			f[x][sz[x]]=d[i]-d[st[x]-1];
			s[i][sz[x]]=min(s[i][sz[x]],f[x][sz[x]]);
			for(register int j=sz[x]+1;j<=n-k;j++){
				f[x][j]=min(f[x][j],s[st[x]][j-sz[x]]+d[i]-d[st[x]-1]);
				s[i][j]=min(s[i][j],f[x][j]);
			}
			ans=min(ans,f[re[i]][n-k]);
		}
	}
	return sum-ans>=0;
}
int main(){
	k=read();n=read();
	for(int i=1,fa;i<=n;i++){
		c[i]=read();v[i]=read();
		fa=read();
		add(fa,i);
	}
	n++;k++;
	dfs(0);
	double l=0,r=10000.0;
	for(register int i=1;i<=25;i++){
		double mid=(l+r)/2.0;
		if(check(mid))
			l=mid;
		else r=mid;
	}
	printf("%.3lf\n",l);
	return 0;
}

本文作者:luckydrawbox

本文链接:https://www.cnblogs.com/luckydrawbox/p/18526469

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   luckydrawbox  阅读(3)  评论(0编辑  收藏  举报  
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起