1. 程式人生 > >牛客wannafly27 C 樹形dp

牛客wannafly27 C 樹形dp

Description


“你,你認錯人了。我真的,真的不是食人魔。”–藍魔法師

給出一棵樹,求有多少種刪邊方案,使得刪後的圖每個連通塊大小小於等於k,兩種方案不同當且僅當存在一條邊在一個方案中被刪除,而在另一個方案中未被刪除,答案對998244353取模
輸入描述:

第一行兩個整數n,k, 表示點數和限制
2 <= n <= 2000, 1 <= k <= 2000
接下來n-1行,每行包括兩個整數u,v,表示u,v兩點之間有一條無向邊
保證初始圖聯通且合法

共一行,一個整數表示方案數對998244353取模的結果

Solution


考慮dp。我們設f[i,j]表示節點i的子樹內,i所在連通塊size為j的方案數
轉移的過程就是把i和兒子們分別合併。合併a和b的過程就是f[a,i]=(Σf[b,j]*f[a,i-j])*f[
可以發現這樣算完只是i所在連通塊size為j的方案數,因此合併完了還要乘上f[b,0],表示f[b]的總的方案數。
注意一下轉移順序就行,這樣dp實際上是n^2的

Code


#include <stdio.h>
#include <string.h>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define fill(x,t) memset(x,t,sizeof(x))

const int MOD=998244353;
const int N=2005;

struct edge {int y,next;} e[N*2];

int size[N],f[N][N];
int ls[N],edCnt,m; int read() { int x=0,v=1; char ch=getchar(); for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar()); for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar()); return x*v; } void add_edge(int x,int y) { e[++edCnt]=(edge) {y,ls[x]}; ls[x]=edCnt; e[++edCnt]=(edge) {
x,ls[y]}; ls[y]=edCnt; } void solve(int now,int fa) { size[now]=1; f[now][1]=1; for (int i=ls[now];i;i=e[i].next) { if (e[i].y==fa) continue; solve(e[i].y,now); drp(j,size[now],1) { drp(k,size[e[i].y],1) { f[now][j+k]=(f[now][j+k]+1LL*f[now][j]*f[e[i].y][k])%MOD; } f[now][j]=1LL*f[now][j]*f[e[i].y][0]%MOD; } size[now]+=size[e[i].y]; } rep(i,1,m) f[now][0]=(f[now][0]+f[now][i])%MOD; } int main(void) { freopen("data.in","r",stdin); int n=read(); m=read(); rep(i,2,n) add_edge(read(),read()); solve(1,0); printf("%d\n", f[1][0]); return 0; }