传送门:P8352 [SDOI/SXOI2022] 小 N 的独立集
题目大意:给定一棵
n
n
n 个点的树,每个点的权值在
1
∼
k
1\sim k
1∼k 之间,问有多少种权值的方案使得整棵树的最大权独立集大小为
m
m
m ,对
m
=
1
∼
n
k
m=1\sim nk
m=1∼nk 分别求解。
因为是全场最简单(果然以我的水平只能出得出来这种送分题了吗),废话不多说直接上题解。
我们都知道树上最大权独立集的求法:
f
[
x
]
[
0
/
1
]
f[x][0/1]
f[x][0/1] 表示以
x
x
x 为根的子树,根是否被选择的答案。
而这里的问题是计数,因此需要dp套dp:设
g
[
x
]
[
p
]
[
q
]
g[x][p][q]
g[x][p][q] 表示以
x
x
x 为根的子树,
f
[
x
]
[
0
]
=
p
f[x][0]=p
f[x][0]=p ,
f
[
x
]
[
1
]
=
q
f[x][1] =q
f[x][1]=q 的方案数。
当然这样状态数直接爆炸了:
p
p
p 和
q
q
q 都是
O
(
n
k
)
O(nk)
O(nk) 级别,状态数直接达到立方。我们先优化状态。
注意到最大权独立集的dp其实还有另一种写法:
f
[
x
]
[
0
/
1
]
f[x][0/1]
f[x][0/1] 表示以
x
x
x 为根的子树, 根是否必须不选 的答案。容易观察到必然有
f
[
x
]
[
1
]
≤
f
[
x
]
[
0
]
≤
f
[
x
]
[
1
]
+
k
f[x][1] \leq f[x][0] \leq f[x][1] + k
f[x][1]≤f[x][0]≤f[x][1]+k ,这是因为限制根必须不选肯定没有不加以限制来得优秀,同时即使选了根也最多会让你的答案增加
k
k
k 。
那么再设计
g
g
g 的时候,就没有必要把
p
p
p 和
q
q
q 都开满了:设
g
[
x
]
[
p
]
[
q
]
g[x][p][q]
g[x][p][q] 表示
x
x
x 的子树,
f
[
x
]
[
1
]
=
p
f[x][1]=p
f[x][1]=p ,
f
[
x
]
[
0
]
=
p
+
q
f[x][0]=p+q
f[x][0]=p+q 的答案,那么
q
q
q 一定在
0
∼
k
0 \sim k
0∼k 中取值。状态数降到了
O
(
n
2
k
2
)
O(n^2k^2)
O(n2k2) 。
转移也比较简单:大力枚举dp值,设
y
y
y 是
x
x
x 的子节点,则
f
[
x
]
[
p
]
[
q
]
×
f
[
y
]
[
p
′
]
[
q
′
]
f[x][p][q] × f[y][p'][q']
f[x][p][q]×f[y][p′][q′] 可以转移到
f
[
x
]
[
p
+
p
′
+
q
′
]
[
m
a
x
(
0
,
q
−
q
′
)
]
f[x][p+p'+q'][max(0,q-q')]
f[x][p+p′+q′][max(0,q−q′)] 。这是因为如果强制不选
x
x
x ,则可以选
y
y
y ,对应的答案是
p
+
p
′
+
q
′
p+p'+q'
p+p′+q′ ;另一种选择是不选
y
y
y ,那么此时可以选
x
x
x ,对应的答案是
p
+
p
′
+
q
p+p'+q
p+p′+q 。
基于树上背包的复杂度分析,可得总的时间复杂度为
O
(
n
2
k
4
)
O(n^2k^4)
O(n2k4) 。
(彩蛋:小N和小
Ω
\Omega
Ω 其实是小Z和小C旋转90度。)
#include<bits/stdc++.h>
using namespace std;
int n,k;
struct edge{
int to,nxt;
}e[2010];
int cnt,fir[1010];
inline void ins(int u,int v){
e[++cnt].to = v;e[cnt].nxt = fir[u];fir[u] = cnt;
e[++cnt].to = u;e[cnt].nxt = fir[v];fir[v] = cnt;
}
int sz[1010];
const int mo = 1000000007;
long long f[1010][5010][6],p[5010][6],ans[5010];
void dfs(int x,int fa){
int i,j,l,g,h;
for(i = 1;i <= k;++i) f[x][0][i] = 1;
sz[x] = k;
for(i = fir[x];i;i = e[i].nxt) if(e[i].to != fa){
int y = e[i].to;
dfs(y,x);
memset(p,0,sizeof(p));
for(j = 0;j <= sz[x];++j){
for(l = 0;l <= k;++l) if(f[x][j][l]){
for(g = 0;g <= sz[y];++g){
for(h = 0;h <= k;++h) if(f[y][g][h]){
(p[j + g + h][max(0,l - h)] += f[x][j][l] * f[y][g][h]) %= mo;
}
}
}
}
memcpy(f[x],p,sizeof(p));
sz[x] += sz[y];
}
}
int main(){
int i,j,u,v;
scanf("%d%d",&n,&k);
for(i = 1;i < n;++i){
scanf("%d%d",&u,&v);
ins(u,v);
}
dfs(1,0);
for(i = 1;i <= n * k;++i){
for(j = 0;j <= i && j <= k;++j) ans[i] += f[1][i - j][j];
printf("%lld\n",ans[i] % mo);
}
return 0;
}