【USST2020 I】Immortal Trees 题解

题目大意

  给定一个 $n$,表示一棵有标号无根树有 $n$ 个结点。
  有如下限制:

  1. 给定 $m$ 个数对 $(x_i,y_i)$,表示树上一定要有 $(x_i,y_i)$ 这条边;
  2. 有 $k$ 个限制 $op_i\ x_i\ deg_i$,若 $op_i=0$ 表示 $x$ 的度数至少为 $deg_i$,若 $op_i=1$ 表示 $x$ 的度数至多为 $deg_i$。

  求合法的树的数量。

  $2 \leq n \leq 60,\ 0 \leq m \leq n-1,\ 0 \leq k \leq 60$
  1s

题解

  有趣~

  看到有标号无根树计数,甚至规定了某些点的度数,八九不离十是考虑 prufer 序。
  但是有些点已经被初始边连起来了,怎么办呢?

  当然是把它们缩起来,一个连通块当作一个新点来考虑啊!

  比如 $n=4$,有一条初始边 $(1,2)$,那么缩起来成为连通块 $a$,我们只需考虑 $a,3,4$ 的 prufer 序。然后,在不同的 prufer 序中,$a$ 连出去的边的数量是不一样的,这会造成不同的贡献。

  因此需要这么 dp:设 $f_{i,j}$ 表示前 $i$ 个连通块共使用了 $j$ 个 prufer 序位置的方案数。转移就是枚举第 $i$ 个连通块用了多少个 prufer 序位置:

  其中 $g_{i,k}$ 表示第 $i$ 个连通块往外连 $k$ 条边的方案数。

  所以现在就是要求 $g_{i,j}$。每个点先求出去掉初始边之后的合法度数区间 $[mindg_i,maxdg_i]$(即除初始边外还需要连这么多边),然后对于每个连通块单独考虑,依次考虑每个点,设 $h_{x,j}$ 表示该连通块前 $x$ 个点用了 $j$ 条边的方案数,那么

  然后就有 $g_{i,j}=h_{size_i,j}$,$size_i$ 表示该连通块的大小。

  这样就是 $O(n^3)$ 的了。(官方题解不知道为什么写了 $O(n^4)$)要再提升的话,就分治 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,&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]);
}