【USST2020 I】Immortal Trees 題解

題目大意

  給定一個 nn,表示一棵有標號無根樹有 nn 個結點。
  有如下限制:

  1. 給定 mm 個數對 (xi,yi)(x_i,y_i),表示樹上一定要有 (xi,yi)(x_i,y_i) 這條邊;
  2. kk 個限制 opi xi degiop_i~x_i~deg_i,若 opi=0op_i=0 表示 xx 的度數至少爲 degideg_i,若 opi=1op_i=1 表示 xx 的度數至多爲 degideg_i

  求合法的樹的數量。

  2n60, 0mn1, 0k602 \leq n \leq 60,~0 \leq m \leq n-1,~0 \leq k \leq 60
  1s

\\
\\
\\

題解

  有趣~

  看到有標號無根樹計數,甚至規定了某些點的度數,八九不離十是考慮 prufer 序。
  但是有些點已經被初始邊連起來了,怎麼辦呢?

  當然是把它們縮起來,一個連通塊當作一個新點來考慮啊!

  比如 n=4n=4,有一條初始邊 (1,2)(1,2),那麼縮起來成爲連通塊 aa,我們只需考慮 a,3,4a,3,4 的 prufer 序。然後,在不同的 prufer 序中,aa 連出去的邊的數量是不一樣的,這會造成不同的貢獻。

  因此需要這麼 dp:設 fi,jf_{i,j} 表示前 ii 個連通塊共使用了 jj 個 prufer 序位置的方案數。轉移就是枚舉第 ii 個連通塊用了多少個 prufer 序位置:
fi,j=k=0j(jk)fi1,jkgi,k+1 f_{i,j}=\sum_{k=0}^j \binom{j}{k} \cdot f_{i-1,j-k} \cdot g_{i,k+1}

  其中 gi,kg_{i,k} 表示第 ii 個連通塊往外連 kk 條邊的方案數。

  所以現在就是要求 gi,jg_{i,j}。每個點先求出去掉初始邊之後的合法度數區間 [mindgi,maxdgi][mindg_i,maxdg_i](即除初始邊外還需要連這麼多邊),然後對於每個連通塊單獨考慮,依次考慮每個點,設 hx,jh_{x,j} 表示該連通塊前 xx 個點用了 jj 條邊的方案數,那麼
hx,j=k=mindgxmaxdgxhx1,jk(jk) h_{x,j}=\sum_{k=mindg_x}^{maxdg_x} h_{x-1,j-k} \cdot \binom{j}{k}

  然後就有 gi,j=hsizei,jg_{i,j}=h_{size_i,j}sizeisize_i 表示該連通塊的大小。

  這樣就是 O(n3)O(n^3) 的了。(官方題解不知道爲什麼寫了 O(n4)O(n^4)要再提升的話,就分治 FFT?

代碼

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;

const int maxn=65;
const LL mo=998244353;

int n,m,k,dg[maxn],mindg[maxn],maxdg[maxn];
LL f[maxn][maxn],g[maxn][maxn];

LL C[maxn][maxn];
void C_pre(int n)
{
	fo(i,0,n)
	{
		C[i][0]=1;
		fo(j,1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
	}
}

int ga[maxn];
int get(int x) {return ga[x]==x ?x :ga[x]=get(ga[x]);}

#define NO {puts("0"); return 0;}

map<pair<int,int>,int> M;
vector<int> V[maxn];
int cc;
LL h[maxn][maxn];
int main()
{
	C_pre(60);
	
	scanf("%d %d %d",&n,&m,&k);
	fo(i,1,n) ga[i]=i, mindg[i]=1, maxdg[i]=n-1;
	fo(i,1,m)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		if (x>y) swap(x,y);
		if (++M[make_pair(x,y)]>1) continue;
		if (get(x)==get(y)) NO;
		ga[get(x)]=get(y);
		dg[x]++, dg[y]++;
	}
	fo(i,1,k)
	{
		int op,x,deg;
		scanf("%d %d %d",&op,&x,&deg);
		if (op==1) maxdg[x]=min(maxdg[x],deg); else mindg[x]=max(mindg[x],deg);
	}
	fo(i,1,n)
	{
		mindg[i]=max(0,mindg[i]-dg[i]), maxdg[i]-=dg[i];
		if (mindg[i]>maxdg[i]) NO;
	}
	
	fo(i,1,n) V[get(i)].push_back(i);
	fo(i,1,n) if (get(i)==i)
	{
		++cc;
		int sz=V[i].size();
		memset(h,0,sizeof(h));
		h[0][0]=1;
		fo(x,1,sz)
		{
			int cur=V[i][x-1];
			fo(curj,mindg[cur],maxdg[cur])
				fo(j,curj,n-1) (h[x][j]+=h[x-1][j-curj]*C[j][curj])%=mo;
		}
		fo(j,0,n-1) g[cc][j]=h[sz][j];
	}
	if (cc==1)
	{
		fo(i,1,n) if (mindg[i]>0) NO;
		puts("1"); return 0;
	}
	
	f[0][0]=1;
	fo(i,1,cc)
		fo(j,0,cc-2)
			fo(k,0,j)
				(f[i][j]+=f[i-1][j-k]*C[j][k]%mo*g[i][k+1])%=mo;
	
	printf("%lld\n",f[cc][cc-2]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章