看到课程标题是 “DP优化”,以为就是斜率优化、单调性之类的……
结果来了些什么奇妙操作
# 题面
给定一棵 (n) 个点的树和整数 (m),每个点有两类权值 (a_i,b_i)。
对于每个 (h=1sim m),求树上的一个独立集 (S),使得 (sumlimits_{iin S}a_ile h),且 (sumlimits_{iin S}b_i) 最大,输出满足上述条件的 (S) 的数量。
数据规模:(nle50,mle5000,1le a_ile m,b_ile 10^6)。
# 解析
题面就描述了一个背包问题,另外有独立集的限制。
如果在原树上直接背包,需要背包合并(由于物品大小并非 (1),虽然两个点只会在 LCA 处产生贡献,但是合并的复杂度仍然是 (mathcal{O}(m^2)) 的),复杂度只能是 (mathcal{O}(nm^2)),无法再优化。
话不多说,我们直接来看看怎么用神仙操作来做这道题。
首先我们建出原树的点分树,点分树有两个(重要的)性质,其中一条我们非常熟悉:
- 树高是 (mathcal{O}(log n))。
另外一条非常显然但是不常用(至少我没见过哪道题用):
- 原树上与 (u) 相邻的点在点分树上只可能是 (mathbf{u}) 的祖先 和 (u) 的后继。
怎么利用这两点来解题?我们考虑按照点分树的 DFS 序 依次决策每个节点是否在独立集中。
这样的话,当我们决策 (u) 时,就不需要考虑 (u) 的后继是否选入独立集,而只需要考虑 (mathbf{u}) 的祖先 有哪些被选入了独立集中 —— (u) 的祖先不多,只有 (mathcal{O}(log n)) 个,于是可以状压:(f_{u,s,i}) 表示的状态为「已经决策了 (u),从根到 (u) 的链上被选入独立集的点是 (s)(压缩状态),且当前背包中装了总重为 (i) 的物品」,需要维护对应的物品价值最大值以及方案数。
那么 (s) 的范围是 (mathcal{O}(2^{log n})=mathcal{O}(n)) 的,(f_{u,s,i}) 的状态数是 (mathcal{O}(n^2m)) 的。由于是 0-1 背包,可以 (mathcal{O}(1)) 转移,总的复杂度也是 (mathcal{O}(n^2m)) 的。
下面关于一些细节简单说一下:
- 知道了祖先的选定状态 (s),如何判断 (u) 是否能加入当前的独立集中?为了保持复杂度必须 (mathcal{O}(1)) 判断:
- 维护压缩状态
lnk[u]
表示当前点分树的祖先中,哪些是原树上与 (u) 相连的节点。 - 判断只需要判断
lnk[u]
和 (s) 是否有交; - 维护只需要在 DFS 进入点 (u) 时枚举原树上与 (u) 相邻的点 (v),更新
lnk[v]
,DFS 退出点 (u) 时在lnk
中撤销 (u) 的信息即可。
- 维护压缩状态
- (f_{u,s,i}) 中压缩状态 (s) 的维护:记 (u) 在 DFS 序上的前驱为 (v),我们需要从 (f_v) 转移到 (f_u)。
- 只需要深度小于 (dep_u) 的祖先;
- 就需要把 (f_{v,s,i}) 的 (s) 中深度大于等于 (dep_u) 的部分删去;
- 具体的,在 DFS 退出点 (u) 时,对每个 (sge 2^{dep_u}),将 (f_{u,s,i}) 贡献到 (f_{u,s-2^{dep_u},i});
- 这样的效果就是递归到当前层时,有用的 (s) 的位数不超过 (dep_v-1)。
大概是一个不错的处理独立集的方法……
# 源代码
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int rin(int &r){
int b=1,c=getchar();r=0;
while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar();
while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar();
return r*=b;
}
const int N=55,M=5005;
#define con(type) const type &
struct Data{
int key;long long cnt;
friend void maxc(Data &a,con(Data)b){
if(a.key<b.key) a=b;
else if(a.key==b.key) a.cnt+=b.cnt;
}
Data operator +(con(int)d)const{return (Data){key+d,cnt};}
}f[2][64][M];
struct Graph{
int head[N],to[N<<1],nxt[N<<1],ncnt;
void init(con(int)sz){for(int i=1;i<=sz;i++)head[i]=0;ncnt=0;}
void addEdge(con(int)u,con(int)v,con(bool)typ=true){
ncnt++;
to[ncnt]=v,nxt[ncnt]=head[u],head[u]=ncnt;
if(typ) ncnt++,to[ncnt]=u,nxt[ncnt]=head[v],head[v]=ncnt;
}
inline int operator [](con(int)u){return head[u];}
}gr;
int ncas,n,m,r_wei,r_root,r_tot,bagsiz,ndfn;
int ara[N],arb[N],nxtto[N];
bool ban[N];
int toggleSize(con(int)u,con(int)fa){
int sizu=1;
for(int it=gr[u];it;it=gr.nxt[it])
if(gr.to[it]!=fa && !ban[gr.to[it]])
sizu+=toggleSize(gr.to[it],u);
return sizu;
}
int findCenter(con(int)u,con(int)fa){
int sizu=1,mxv=0;
for(int it=gr[u];it;it=gr.nxt[it]){
int v=gr.to[it],sizv;
if(v==fa || ban[v]) continue;
sizv=findCenter(v,u);
sizu+=sizv,mxv=max(mxv,sizv);
}
mxv=max(mxv,r_tot-sizu);
if(r_wei>mxv) r_wei=mxv,r_root=u;
return sizu;
}
void dacDFS(con(int)u,con(int)dep){
// # Marks
for(int it=gr[u];it;it=gr.nxt[it]) nxtto[gr.to[it]]|=1<<dep;
ban[u]=true;
// # DP
int I=(++ndfn)&1;
for(int s=0,ss=1<<(dep+1);s<ss;s++)
for(int i=0,ii=m;i<=ii;i++)
f[I][s][i].key=-1,f[I][s][i].cnt=0;
if(dep){
for(int s=0,ss=1<<dep;s<ss;s++)
for(int i=0;i<=bagsiz;i++)
f[I][s][i]=f[!I][s][i];
int ii=min(bagsiz,m-ara[u]);
for(int s=0,ss=1<<dep;s<ss;s++){
if(nxtto[u]&s) continue;
for(int i=0;i<=ii;i++)
if(~f[!I][s][i].key)
maxc(f[I][s|(1<<dep)][i+ara[u]],f[!I][s][i]+arb[u]);
}
}
else{
f[I][0][0].key=0,f[I][0][0].cnt=1;
f[I][1][ara[u]].key=arb[u],f[I][1][ara[u]].cnt=1;
}
bagsiz=min(m,bagsiz+ara[u]);
// # Transport
for(int it=gr[u];it;it=gr.nxt[it]){
int v=gr.to[it],sizv;
if(ban[v]) continue;
sizv=toggleSize(v,0);
r_tot=sizv,r_wei=n+1,findCenter(v,0);
int rtv=r_root;
// printf("%d -> %d
",u,v);
dacDFS(rtv,dep+1);
}
// # Roll-back
for(int it=gr[u];it;it=gr.nxt[it]) nxtto[gr.to[it]]^=1<<dep;
ban[u]=false;
// # Toggle-DP
I=ndfn&1;
for(int s=1<<dep,ss=1<<(dep+1);s<ss;s++)
for(int i=0;i<=bagsiz;i++)
maxc(f[I][s^(1<<dep)][i],f[I][s][i]);
}
int main(){
rin(ncas);
for(int cas=1;cas<=ncas;cas++){
rin(n),rin(m);
// # Clear
gr.init(n);
bagsiz=ndfn=0;
// # Read & Init-build
for(int i=1;i<=n;i++) rin(ara[i]),rin(arb[i]);
for(int i=1,u,v;i<n;i++) gr.addEdge(rin(u),rin(v));
// # Calculate
r_tot=n,r_wei=n+1,findCenter(1,0);
int rt=r_root;
dacDFS(rt,0);
// # Print
printf("Case %d:
",cas);
int I=ndfn&1;
for(int i=1;i<=m;i++) printf("%lld%c",f[I][0][i].cnt,i==m?'
':' ');
}
return 0;
}