【JZOJ5870】【NOIP2018模拟9.15】地图 (递推+DP+组合数学)

Problem

这里写图片描述

Hint

这里写图片描述

Solution

  • 首先,图中只会存在链和环。
  • 记图中有one个度数为1的点,two个度数为2的点。囿于每条链有两个度数为1的点(链的两端),链的数量是确定的:one2\frac{one}2
  • 这时,我灵(nao)光(zi)一(wa)闪(te),想到了一个优(sha)美(bi)的方法。

我的SB方法:递推+组合数学+容斥

  • 观察到链和环的方案相对独立。那么根据乘法原理,我们可以求出链的方案tot1,环的方案tot2,总方案即为tot1*tot2。
  • 那么分类讨论一下。
链的情况
  • 我们先令one个点两两匹配,构成one2\frac{one}2条只含两个点的链。
  • 不妨枚举当前有n条链。假设增加一条,则点数增加为2*n+2。
  • 枚举点1连接的是哪个点,这有2n+1种可能;而剩下的2n个点,两两匹配成i条链。
  • 因此,递推式为hn+1=(2n+1)hnh_{n+1}=(2*n+1)*h_n,其中n为链数。

  • 现在,我们再将度数为2的点插入到这些链中。
  • gig_i表示有i个度数为2的点在链中。新插一个点,我们可以插在所有链的非链首节点的左边。譬如下图:
    在这里插入图片描述
  • 我们可以插在任意一个蓝点左边,因此有3+2=5种方案。实际上,记有k条链,第i条链的链长为lenilen_i,插入新点的方案即为i=1kleni1\sum_{i=1}^k len_i-1。 不难发现,这其实等于已插入的点数+链数
  • 因此,gi=gi1(i1+one2)g_i=g_{i-1}*(i-1+\frac{one}2),其中one2\frac{one}2为链数。

环的情况

  • 环的情况就有些复杂了。
  • 考虑DP。设fi,jf_{i,j}表示i个度数为2的点在环上,其中有j个一元环的方案数。
  • 囿于原图不存在自环,我们最终得到的应是fi,0f_{i,0}
  • 然后可以得到三种转移:1.新建一个一元环;2.令当前点加入到一个一元环中;3.令当前点加入到一个多元环中。

  • 然而还有一个坑点——那就是环翻转一下,和原来全等,但是我们会算重。
  • 不妨在新建环的时候,就将其贡献记为12\frac 12,这样最终算出的结果便是去了重的。
  • 然而,我们这样只能算出无自环的方案数,不能算出无二元环(重边)的方案数。

  • 考虑容斥。
  • 枚举有i个度数为2的点在环上,其中有j个二元环。那么正负性为(1)j(-1)^j,系数为Ci2jhj2jC_i^{2j}*h_j*2^{-j},其中hjh_j为上述提到的,点两两匹配的方案数。
  • 系数中有个2j2^{-j}的原因是我们把二元环的贡献都算成了12\frac 12(建环时是12\frac 12,再插一个点是1,121=12\frac 12*1=\frac 12),然而二元环的贡献应是1;于是在去掉二元环的方案中,我们应该也乘回这些12\frac 12以弥补二元环的缺失。
  • 然后对于每个i,都求一波j=0i2(1)jCi2jhj2jfi2j,0\sum_{j=0}^{\lfloor \frac i2 \rfloor} (-1)^j*C_i^{2j}*h_j*2^{-j}*f_{i-2j,0},而这便是真正的fif_i(无自环、无二元环的方案数)。

  • 时间复杂度:O(n2)O(n^2)

Code

#include <bits/stdc++.h>
#define P(x,y) x=((x)+(y))%mo
#define T(x,y) x=((x)*(y))%mo
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;

const int N=4001;
const ll mo=998244353;
int k,n,d,one,two;
ll i,j,ls,dou[N],lian[N],div2,f[N][N],g,xs,C[N][N],zf,ans;

ll fpow(ll x,ll y)
{
	ll ans=1;
	for(;y;y>>=1,T(x,x)) if(y&1)T(ans,x);
	return ans;
}

int main()
{
	freopen("map.in","r",stdin);
	freopen("map.out","w",stdout);
	scanf("%d",&n);
	fo(i,1,n) 
	{
		scanf("%d",&d);
		d&1 ? one++ : two++;
	}
	if(one&1) {puts("0"); return 0;}
	
	dou[0]=1;
	fo(i,1,n) dou[i]=dou[i-1]*(i*2-1)%mo;
	lian[0]=dou[ls=one>>1];
	fo(i,1,two) lian[i]=lian[i-1]*(ls+i-1)%mo;
	
	f[0][0]=1; div2=fpow(2,mo-2);
	fo(i,0,two-1)
		fo(j,0,i)
			if(f[i][j])
			{
				P(f[i+1][j+1],f[i][j]*div2);
				P(f[i+1][j],f[i][j]*(i-j));
				if(j) P(f[i+1][j-1],f[i][j]*j);
			}
	
	fo(i,0,two)
	{
		C[i][0]=1;
		fo(j,1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo;
	}
	
	fo(i,0,two) 
	{
		g=0;
		fo(j,0,i>>1)
		{
			xs=C[i][j<<1]*dou[j]%mo*fpow(div2,j)%mo;
			zf=(j&1?-1:1);
			P(g,zf*xs*f[i-j*2][0]); P(g,mo);
		}
		
		P(ans,g*C[two][i]%mo*lian[two-i]);
	}
	
	printf("%lld",ans);
}

一个更为舒服的方法

  • 实际上,这道题一个DP就解决了。
  • fi,jf_{i,j}表示i个度数为2的点,其中j个点在环上(即剩下的i-j个点在链上)的方案数。
  • 有以下三种转移:
    fi,j={fi3,j3Ci12fi1,j1(j1)ifi1,j   (ij1+one2)i f_{i,j}=\left\{ \begin{aligned} &amp;f_{i-3,j-3} *C_{i-1}^2 &amp; &amp; 新建三元环 \\ &amp;f_{i-1,j-1} *(j-1) &amp; &amp; 将点i插入一个环中 \\ &amp;f_{i-1,j} \ \ \ *(i-j-1+\frac{one}2) &amp; &amp; 将点i插入一条链中 \end{aligned} \right.
  • 新建环的贡献为何是Ci12C_{i-1}^2呢?我们假定点i就在新环内,然后从剩下的i-1个点中选2个出来陪它。如若不然,则有可能算重。然后不必再因会翻转除以2,因为三元环定是唯一的。

  • 这样的话,ans=i=0twoftwo,ians=\sum_{i=0}^{two} f_{two,i},其中two为度数为2的节点的个数。
  • 但还没把链首、链尾两两匹配的方案数算上,所以最后要再乘上。
  • 时间复杂度:O(n2)O(n^2)

Code

#include <bits/stdc++.h>
#define P(x,y) x=(x+y)%mo
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;

const int N=2001;
const ll mo=998244353;
int n,d,two;
ll i,j,one,f[N][N],ans;

int main()
{
	freopen("map.in","r",stdin);
	freopen("map.out","w",stdout);
	scanf("%d",&n);
	fo(i,1,n)
	{
		scanf("%d",&d);
		d&1?one++:two++;
	}
	if(one&1) {puts("0"); return 0;}
	f[0][0]=1;
	fo(i,1,two)
		fo(j,0,i)
		{
			if(j>=3) f[i][j]=f[i-3][j-3]*((i-1)*(i-2)>>1)%mo;
			if(j>=1) P(f[i][j],f[i-1][j-1]*(j-1));
			P(f[i][j],f[i-1][j]*(one/2+i-j-1));
		}
	fo(i,0,two) P(ans,f[two][i]);
	fo(i,3,one) if(i&1) (ans*=i)%=mo;
	printf("%lld",ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章