牛客网 Wannafly挑战赛27 蓝魔法师
蓝魔法师
链接:
https://www.nowcoder.com/acm/contest/215/C
来源:牛客网
题目描述
“你,你认错人了。我真的,真的不是食人魔。”--蓝魔法师
给出一棵树,求有多少种删边方案,使得删后的图每个连通块大小小于等于\(k\),两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对\(998244353\)取模
输入描述:
第一行两个整数\(n\),\(k\), 表示点数和限制
\(2 \le n \le 2000, 1 \le k \le 2000\)
接下来\(n-1\)行,每行包括两个整数\(u\),\(v\),表示\(u\),\(v\)两点之间有一条无向边
输出描述:
共一行,一个整数表示方案数对\(998244353\)取模的结果
长见识了,比赛交了\(8\)次最后终于过了。
树上分组背包,令\(dp_{i,j}\)代表子树\(i\)还剩余\(j\)个点可以连出去时的方案数。
这个转移每个人的写法都可以不一样,说一下我的。
初始化\(dp_{i,1}=1\),代表先强制选\(i\)
对每个子树做\(dp_{i,j}=dp_{s,k}\times dp_{i,j-k}\)表示连接
特判直接砍掉子树的情况,在做子树之前\(dp_{i,j}=dp_{i,j}\times dp_{s,0}\)
做完后再处理一下自己被砍的情况。
注意倒序枚举。
这样写出来是这样的。
void dfs(int now,int fa)
{
dp[now][1]=1;
siz[now]++;
for(int i=head[now];i;i=Next[i])
{
int v=to[i];
if(v==fa) continue;
dfs(v,now);
siz[now]+=siz[v];
for(int r=min(k,siz[now]);r;r--)
{
(dp[now][r]*=dp[v][0])%=mod;
for(int l=1;l<=min(r,siz[v]);l++)
(dp[now][r]+=dp[v][l]*dp[now][r-l])%=mod;
}
}
for(int i=1;i<=min(k,siz[now]);i++) (dp[now][0]+=dp[now][i])%=mod;
}
很不幸的是这样写可以轻松被链卡到\(O(n^3)\)
有一种写法是\(O(n^2)\)的
先枚举子树大小那么大,然后更新当前树
void dfs(int now,int fa)
{
dp[now][1]=1;
siz[now]++;
for(int i=head[now];i;i=Next[i])
{
int v=to[i];
if(v==fa) continue;
dfs(v,now);
for(int r=1;r<=min(k,siz[now]+siz[v]);r++)
f[r]=dp[now][r],(dp[now][r]*=dp[v][0])%=mod;
for(int l=1;l<=siz[v];l++)//物品
for(int r=min(k,siz[now]+l);r>=l+1;r--)//被转移的位置
(dp[now][r]+=dp[v][l]*f[r-l])%=mod;
siz[now]+=siz[v];
}
int rr=min(k,siz[now]);
for(int i=1;i<=rr;i++) (dp[now][0]+=dp[now][i])%=mod;
}
值得一提的是,这个跑法的复杂度是非常严格的,随机数据下进入核心转移的地方只有\(\frac{n(n-1)}{2}\)次,怎么构造都是这样(当然按不考虑\(k\)的大小的剪枝)
为什么呢?考虑一种感性的理解方式。
每一对点,当且仅当在它们的\(\tt{LCA}\)时,能够被转移。
考虑修补一下第一种做法,发现多余的转移是因为权值为\(0\),如果我们这样写,那么复杂度也是一样的了
void dfs(int now,int fa)
{
dp[now][1]=1;
siz[now]++;
for(int i=head[now];i;i=Next[i])
{
int v=to[i];
if(v==fa) continue;
dfs(v,now);
siz[now]+=siz[v];
for(int r=min(k,siz[now]);r;r--)
{
(dp[now][r]*=dp[v][0])%=mod;
if(r==1) continue;
for(int l=siz[v]-siz[now]+r;l<=min(min(k,r-1),siz[v]);l++)
(dp[now][r]+=dp[v][l]*dp[now][r-l])%=mod;
}
}
for(int i=1;i<=k;i++) (dp[now][0]+=dp[now][i])%=mod;
}
2018.10.27