【YbtOJ#20064】预算缩减
题目
题目链接:http://noip.ybtoj.com.cn/contest/90/problem/2
给定一棵树,你需要删去一些边(可以不删),使得剩下的图中每个点所在的连通块大小都 \(\geq m\)。
求删边的方案数,对 \(786433\) 取模。两种方案不同,当且仅当存在一条边在一个方案中被删去,而在另外一个方案中没有被删去。
思路
设 \(f[x][i]\) 表示在 \(x\) 子树中,\(x\) 所在连通块大小为 \(i\),其他联通块大小全部不小于 \(m\) 的方案数。
考虑加入一棵子树 \(y\) 的时候,有
\[f[x][i+j]=f'[x][i]+f'[x][i]\times f[v][j]
\]
\[f[x][i]=f'[x][i]+f'[x][i]\times f[v][j]\ (j\geq m)
\]
其中变量只需要枚举到子树大小,这样复杂度等价于枚举了子树的两个点,而每两个点在 LCA 处才会被枚举。时间复杂度 \(O(n^2)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5010,MOD=786433;
int n,m,tot,head[N],size[N];
ll ans,f[N][N],g[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
void dfs(int x,int fa)
{
size[x]=f[x][1]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
dfs(v,x);
for (int j=1;j<=size[x]+size[v];j++)
g[j]=f[x][j],f[x][j]=0;
for (int j=1;j<=size[x];j++)
for (int k=1;k<=size[v];k++)
{
f[x][j+k]=(f[x][j+k]+f[v][k]*g[j])%MOD;
if (k>=m) f[x][j]=(f[x][j]+f[v][k]*g[j])%MOD;
}
size[x]+=size[v];
}
}
}
int main()
{
freopen("cut.in","r",stdin);
freopen("cut.out","w",stdout);
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
dfs(1,0);
for (int i=m;i<=n;i++)
ans=(ans+f[1][i])%MOD;
printf("%lld",ans);
return 0;
}