【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;
}
原文地址:https://www.cnblogs.com/stoorz/p/13803856.html