「SOL」The Hanged Man(HDU)
看到课程标题是 “DP优化”,以为就是斜率优化、单调性之类的……
结果来了些什么奇妙操作
# 题面
给定一棵 \(n\) 个点的树和整数 \(m\),每个点有两类权值 \(a_i,b_i\)。
对于每个 \(h=1\sim m\),求树上的一个独立集 \(S\),使得 \(\sum\limits_{i\in S}a_i\le h\),且 \(\sum\limits_{i\in S}b_i\) 最大,输出满足上述条件的 \(S\) 的数量。
数据规模:\(n\le50,m\le5000,1\le a_i\le m,b_i\le 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\) 时,对每个 \(s\ge 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\n",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:\n",cas);
int I=ndfn&1;
for(int i=1;i<=m;i++) printf("%lld%c",f[I][0][i].cnt,i==m?'\n':' ');
}
return 0;
}