题目大意
给定一个 n,表示一棵有标号无根树有 n 个结点。
有如下限制:
- 给定 m 个数对 (xi,yi),表示树上一定要有 (xi,yi) 这条边;
- 有 k 个限制 opi xi degi,若 opi=0 表示 x 的度数至少为 degi,若 opi=1 表示 x 的度数至多为 degi。
求合法的树的数量。
2≤n≤60, 0≤m≤n−1, 0≤k≤60
1s
题解
有趣~
看到有标号无根树计数,甚至规定了某些点的度数,八九不离十是考虑 prufer 序。
但是有些点已经被初始边连起来了,怎么办呢?
当然是把它们缩起来,一个连通块当作一个新点来考虑啊!
比如 n=4,有一条初始边 (1,2),那么缩起来成为连通块 a,我们只需考虑 a,3,4 的 prufer 序。然后,在不同的 prufer 序中,a 连出去的边的数量是不一样的,这会造成不同的贡献。
因此需要这么 dp:设 fi,j 表示前 i 个连通块共使用了 j 个 prufer 序位置的方案数。转移就是枚举第 i 个连通块用了多少个 prufer 序位置:
fi,j=k=0∑j(kj)⋅fi−1,j−k⋅gi,k+1
其中 gi,k 表示第 i 个连通块往外连 k 条边的方案数。
所以现在就是要求 gi,j。每个点先求出去掉初始边之后的合法度数区间 [mindgi,maxdgi](即除初始边外还需要连这么多边),然后对于每个连通块单独考虑,依次考虑每个点,设 hx,j 表示该连通块前 x 个点用了 j 条边的方案数,那么
hx,j=k=mindgx∑maxdgxhx−1,j−k⋅(kj)
然后就有 gi,j=hsizei,j,sizei 表示该连通块的大小。
这样就是 O(n3) 的了。(官方题解不知道为什么写了 O(n4))要再提升的话,就分治 FFT?
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #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]); }
|