「SOL」The Hanged Man(HDU) | Lucky_Glass's Blog
0%

「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$。

大概是一个不错的处理独立集的方法……


# 源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*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;
}

THE END

Thanks for reading!

谁留下 烽火连天照夜的勾划
又是谁在传说中寻他
千年后 挥兵破阵 不过一刹那
血雨腥风吻过了伤疤

——《山河令》By 忘川风华录 / 星尘

> Link 山河令-网易云