bzoj4910/洛谷P3780/loj2268 [SDOI2017]苹果树 DP ——litble

题目分析

题目可以看做,每拿一个苹果要花一元钱,只有 k k k元钱,每一个深度上都有一个苹果可以免费获得,问可以获得的最大幸福度。

由于每个深度都能有一个苹果免费获得,所以肯定存在一个叶子节点,它上面的苹果被取了至少一个。因此,也可以看做将一条从根到叶子的链上的苹果都免费取一个,剩下的再做背包。取链的方案只有 O ( n ) O(n) O(n)种。

可是链上的节点,每个节点都只能免费取一个苹果,剩下的那些怎么办?将这条链上的 a i a_i ai都减1然后重新DP一次?

于是将所有节点拆点,拆为存在 1 1 1个苹果的点和存在 a i − 1 a_i-1 ai1个苹果的点,后者是前者的儿子,也就是,这个点要取了第一个苹果后,才能取第二个苹果。

接下来使用dfs序出序的手法,也就是dfs这棵树,当从一个节点回溯向它的父亲时,给这个节点编号。

若枚举的这条链的叶子(链上出序最小节点)的出序是 p o s i pos_i posi,那么很显然出序在 [ 1 , p o s i ] [1,pos_i] [1,posi]都是不在链上的节点。若将每一个节点往下dfs儿子的顺序反过来再做一遍dfs,那么出序在 [ 1 , p o s i ′ ] [1,pos'_i] [1,posi]区间内的点和原来出序在 [ 1 , p o s i ] [1,pos_i] [1,posi]的点,正好就是(叶子被拆出来的第二个点)+(不在链上的点)

于是我们可以对于两种dfs出序序列,前缀都做DP, f ( i , j ) f(i,j) f(i,j)表示前 i i i个节点取 j j j个苹果的最大幸福度。设 s z p i sz_{p_i} szpi表示点 p i p_i pi的子树大小,根据若这个点不取苹果,则它的子树不能取苹果,而子树的dfs出序跟它的又是连着的,可以得到DP方程: f ( i , j ) = m a x ( f ( i − 1 , j − t ) + t v p i , f ( i − s z p i , j ) ) f(i,j)=max(f(i-1,j-t)+tv_{p_i},f(i-sz_{p_i},j)) f(i,j)=max(f(i1,jt)+tvpi,f(iszpi,j))

类似多重背包单调队列优化的手法,这个也可以单调队列优化。

然后枚举链,枚举用两种出序划分开的两个集合各取多少个苹果,合并信息,得到答案。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=40005;
int kas,n,SZ,K,tim1,tim2,ans;
vector<int> tr[N];
int not_leaf[N],a[N],v[N],fa[N],Lsum[N];
int pos1[N],pos2[N],repos1[N],repos2[N],sz[N];
int f1[60000005],f2[60000005],q[500005];

void dfs1(int x) {
	sz[x]=1;
	for(RI i=0;i<tr[x].size();++i)
		Lsum[tr[x][i]]=Lsum[x]+v[tr[x][i]],dfs1(tr[x][i]),sz[x]+=sz[tr[x][i]];
	pos1[x]=++tim1,repos1[tim1]=x;
}
void dfs2(int x) {
	for(RI i=0;i<tr[x].size();++i) dfs2(tr[x][i]);
	pos2[x]=++tim2,repos2[tim2]=x;
}
void DP(int *p,int *f) {
	for(RI j=1;j<=K;++j) f[j+1]=-0x3f3f3f3f;
	for(RI i=1;i<=SZ;++i) {
		int x=p[i],he=1,ta=0;
		for(RI j=0;j<=K;++j) {
			while(he<=ta&&j-q[he]>a[x]) ++he;
			int now=i*(K+1)+j+1;
			f[now]=f[(i-sz[x])*(K+1)+j+1];
			if(he<=ta) f[now]=max(f[now],f[(i-1)*(K+1)+q[he]+1]+(j-q[he])*v[x]);
			while(he<=ta&&f[(i-1)*(K+1)+q[ta]+1]-q[ta]*v[x]<=
				f[(i-1)*(K+1)+j+1]-j*v[x]) --ta;
			q[++ta]=j;
		}
	}
}
void work() {
	Lsum[1]=v[1],dfs1(1);
	for(RI i=1;i<=n;++i) reverse(tr[i].begin(),tr[i].end());
	dfs2(1),DP(repos1,f1),DP(repos2,f2);
	for(RI i=1;i<=n;++i) {
		if(not_leaf[i]) continue;
		for(RI j=0;j<=K;++j) {
			ans=max(ans,f1[(pos1[i]-1)*(K+1)+j+1]+
				f2[(pos2[i]-sz[i])*(K+1)+K-j+1]+Lsum[i]);//子树出现多次
		}
	}
	printf("%d\n",ans);
}
void clear_all() {
	tim1=tim2=ans=0;
	for(RI i=1;i<=SZ;++i) tr[i].clear(),not_leaf[i]=0;
	for(RI i=0;i<=(SZ+1)*(K+1);++i) f1[i]=f2[i]=0;
}
int main()
{
	kas=read();
	while(kas--) {
		SZ=n=read(),K=read();
		for(RI i=1;i<=n;++i)
			fa[i]=read(),not_leaf[fa[i]]=1,a[i]=read(),v[i]=read();
		for(RI i=1;i<=n;++i) {
			if(fa[i]) tr[fa[i]].push_back(i);
			if(a[i]>1) ++SZ,a[SZ]=a[i]-1,v[SZ]=v[i],a[i]=1,tr[i].push_back(SZ);
		}
		work(),clear_all();
	}
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值