【USST2020 I】Immortal Trees 题解

题目大意

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

  1. 给定 m m m 个数对 ( x i , y i ) (x_i,y_i) (xi,yi),表示树上一定要有 ( x i , y i ) (x_i,y_i) (xi,yi) 这条边;
  2. k k k 个限制 o p i   x i   d e g i op_i~x_i~deg_i opi xi degi,若 o p i = 0 op_i=0 opi=0 表示 x x x 的度数至少为 d e g i deg_i degi,若 o p i = 1 op_i=1 opi=1 表示 x x x 的度数至多为 d e g i deg_i degi

  求合法的树的数量。

   2 ≤ n ≤ 60 ,   0 ≤ m ≤ n − 1 ,   0 ≤ k ≤ 60 2 \leq n \leq 60,~0 \leq m \leq n-1,~0 \leq k \leq 60 2n60, 0mn1, 0k60
  1s

\\
\\
\\

题解

  有趣~

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

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

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

  因此需要这么 dp:设 f i , j f_{i,j} fi,j 表示前 i i i 个连通块共使用了 j j j 个 prufer 序位置的方案数。转移就是枚举第 i i i 个连通块用了多少个 prufer 序位置:
f i , j = ∑ k = 0 j ( j k ) ⋅ f i − 1 , j − k ⋅ g i , k + 1 f_{i,j}=\sum_{k=0}^j \binom{j}{k} \cdot f_{i-1,j-k} \cdot g_{i,k+1} fi,j=k=0j(kj)fi1,jkgi,k+1

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

  所以现在就是要求 g i , j g_{i,j} gi,j。每个点先求出去掉初始边之后的合法度数区间 [ m i n d g i , m a x d g i ] [mindg_i,maxdg_i] [mindgi,maxdgi](即除初始边外还需要连这么多边),然后对于每个连通块单独考虑,依次考虑每个点,设 h x , j h_{x,j} hx,j 表示该连通块前 x x x 个点用了 j j j 条边的方案数,那么
h x , j = ∑ k = m i n d g x m a x d g x h x − 1 , j − k ⋅ ( j k ) h_{x,j}=\sum_{k=mindg_x}^{maxdg_x} h_{x-1,j-k} \cdot \binom{j}{k} hx,j=k=mindgxmaxdgxhx1,jk(kj)

  然后就有 g i , j = h s i z e i , j g_{i,j}=h_{size_i,j} gi,j=hsizei,j s i z e i size_i sizei 表示该连通块的大小。

  这样就是 O ( n 3 ) O(n^3) O(n3) 的了。(官方题解不知道为什么写了 O ( n 4 ) O(n^4) O(n4)要再提升的话,就分治 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]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值