【洛谷5391】[Cnoi2019] 青染之心(操作树树剖+背包)
- 有一个大小为\(n\)的背包。
- \(m\)次操作,每次在物品序列末尾添加一种体积\(a_i\)、价值\(b_i\)的物品或删除序列末尾的一种物品。
- 每种物品都有无限个,要求在每次操作后求出背包可以装下的最大价值和。
- \(n,m\le2\times10^4\)
操作树树剖
非常有意思的一道题目。
很容易想到做\(m\)次背包,这一时间复杂度也确实无法继续优化了。
但是,这题的核心问题在于内存,我们不可能开出大小为\(O(nm)\)的数组。
解决方法是建出操作树,然后对操作树树剖。
注意到一个点到根节点路径上重链切换的次数是\(O(logn)\)的。
当我们处理到一个点时,先对所有轻儿子新开一个数组\(DP\),最后对重儿子可以直接用这个数组继续\(DP\),也就是说只需对当前点到根节点路径上的每条重链存一个\(DP\)数组,内存就是\(O(mlogn)\)的了。
代码:\(O(nm)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 20000
#define LN 15
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,a[N+5],b[N+5],id[N+5],ans[N+5],St[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
int g[N+5],sz[N+5];I void dfs(CI x)//树链剖分
{
sz[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) dfs(e[i].to),sz[x]+=sz[e[i].to],(!g[x]||sz[e[i].to]>sz[g[x]])&&(g[x]=e[i].to);
}
int f[LN+1][N+5];I void DP(CI x,CI p)//动态规划
{
RI i,j;for(i=a[x];i<=n;++i) f[p][i]=max(f[p][i],f[p][i-a[x]]+b[x]);if(ans[x]=f[p][n],!g[x]) return;//加入当前点的物品
for(i=lnk[x];i;i=e[i].nxt) if(e[i].to^g[x]) {for(j=0;j<=n;++j) f[p+1][j]=f[p][j];DP(e[i].to,p+1);}DP(g[x],p);//先DP轻儿子,再DP重儿子
}
int main()
{
RI i,A,B,T=0;char s[10];for(scanf("%d%d",&m,&n),i=1;i<=m;id[i++]=St[T])
scanf("%s",s),s[0]=='a'?(scanf("%d%d",a+i,b+i),add(St[T],i),St[++T]=i):--T;//建操作树
for(dfs(0),DP(0,0),i=1;i<=m;++i) printf("%d\n",ans[id[i]]);return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒