題目大意
給定一個 ,表示一棵有標號無根樹有 個結點。
有如下限制:
- 給定 個數對 ,表示樹上一定要有 這條邊;
- 有 個限制 ,若 表示 的度數至少爲 ,若 表示 的度數至多爲 。
求合法的樹的數量。
1s
題解
有趣~
看到有標號無根樹計數,甚至規定了某些點的度數,八九不離十是考慮 prufer 序。
但是有些點已經被初始邊連起來了,怎麼辦呢?
當然是把它們縮起來,一個連通塊當作一個新點來考慮啊!
比如 ,有一條初始邊 ,那麼縮起來成爲連通塊 ,我們只需考慮 的 prufer 序。然後,在不同的 prufer 序中, 連出去的邊的數量是不一樣的,這會造成不同的貢獻。
因此需要這麼 dp:設 表示前 個連通塊共使用了 個 prufer 序位置的方案數。轉移就是枚舉第 個連通塊用了多少個 prufer 序位置:
其中 表示第 個連通塊往外連 條邊的方案數。
所以現在就是要求 。每個點先求出去掉初始邊之後的合法度數區間 (即除初始邊外還需要連這麼多邊),然後對於每個連通塊單獨考慮,依次考慮每個點,設 表示該連通塊前 個點用了 條邊的方案數,那麼
然後就有 , 表示該連通塊的大小。
這樣就是 的了。(官方題解不知道爲什麼寫了 )要再提升的話,就分治 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,°);
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]);
}