[ACNOI2022]坚持容斥

280 篇文章 1 订阅

题目

题目背景
你以为这是 2022 2022 2022 年,其实这是 2122 2122 2122 年。每个人出生时,意识就会被输送进 the matrix \text{the matrix} the matrix 里,作为卷爷的电池而存在。

Welcome to the real world,  OneInDark . \text{Welcome to the real world, }\textsf{OneInDark}. Welcome to the real world, OneInDark.

题目描述
一个 2-regular \text{2-regular} 2-regular 图是一个有向图,满足每个点的出度和入度都是 2 2 2,且没有重边。注意 a → b a\to b ab b → a b\to a ba 可以同时存在。

现在,规定有向边 i → p i    ( i ∈ S ) i\to p_i\;(i\in S) ipi(iS) 不存在,请你求出这样的 2 -regular 2\text{-regular} 2-regular 图数量。

数据范围与约定
n ⩽ 500 n\leqslant 500 n500 p i ∈ [ 1 , n ] p_i\in[1,n] pi[1,n]

思路

出度入度拆点,转化为二分图环剖分。另一道题 矩阵 S = ∅ S=\varnothing S= 的原题。考虑怎么把限制处理掉。不能选是比较麻烦的。考虑 容斥,转化为必须选择。

X \frak X X 部为代表出度的点, Y \frak Y Y 部则相反。那么每个 Y \frak Y Y 部的点,要么已连了两条边,要么连了一条,要么没有。设三类点数量分别为 a , b , a + c a,b,a{+}c a,b,a+c,也就是说, c c c X \frak X X 部中没有连边的点数。首先我们需求出方案数。

不难发现, c c c a a a 是几乎等效的:走到 c c c 中某个后,必须接着走两条边,并不改变所在的 X / Y \frak X/\frak Y X/Y 部。但是可以定向,相当于求出有向环数量。乘系数 2 c 2^c 2c 即可。

所以现在共有 ( a + c ) (a{+}c) (a+c) 个单点。走到单点上,可以切换 X / Y \frak X/\frak Y X/Y 部,而走 b b b 则不行。所以 a a a 先随便排列,再将 b b b 填入即可。为了避免重复,规定 b b b 在开头已使用过一次。于是方案数为 [ ( a + c ) ! ] 2 × ( 2 a + 2 c + b − 1 b − 1 ) × ( b − 1 ) ! [(a{+}c)!]^2\times{2a+2c+b-1\choose b-1}\times(b{-}1)! [(a+c)!]2×(b12a+2c+b1)×(b1)! 。我们惊讶地发现 b = 0 b=0 b=0 该式也成立;或许是个巧合。无论如何,方案数为
2 c ⋅ [ ( a + c ) ! ] 2 ( 2 a + 2 c + b − 1 ) ! ( 2 a + 2 c ) ! ( c ≠ 0 ∨ a + b ⩾ 2 ) 2^c\cdot[(a{+}c)!]^2\frac{(2a{+}2c{+}b{-}1)!}{(2a{+}2c)!}\quad(c\ne 0\lor a{+}b\geqslant 2) 2c[(a+c)!]2(2a+2c)!(2a+2c+b1)!(c=0a+b2)

看上去 a , c a,c a,c 是完全等价的,可惜有一个不同的取值范围——单独的 a a a b b b 都不对应合法方案,但单独的 c c c 就可以!——但二者具有相当高的相似性,没准真的可以?如果有人由此搞出更优做法,希望可以分享 😄

所以还是要引入 三元生成函数。怎么写成指数型?要看组合数怎样构成。不难发现,组合数的构成应该是 ( a i ) ( b j ) ( c k ) ( a + c i + k ) {a\choose i}{b\choose j}{c\choose k}{a+c\choose i+k} (ia)(jb)(kc)(i+ka+c),因为在 Y \frak Y Y 部中有 ( a + c ) (a{+}c) (a+c) 个单点。所以生成函数的系数需除以 i ! j ! k ! ( i + k ) ! i!j!k!(i{+}k)! i!j!k!(i+k)! 。稍微化简一下即有
F ( x , y , z ) = ∑ i , j , k ( 2 i + 2 k + j − 1 ) ! ( 2 i + 2 k ) ! × j ! ( i + k i ) 2 k x i y j z k − x 2 − y F(x,y,z)=\sum_{i,j,k}\frac{(2i{+}2k{+}j{-}1)!}{(2i{+}2k)!\times j!}{i{+}k\choose i}2^{k}x^iy^jz^k-\frac{x}{2}-y F(x,y,z)=i,j,k(2i+2k)!×j!(2i+2k+j1)!(ii+k)2kxiyjzk2xy

答案就是 G ( x , y , z ) = exp ⁡ [ F ( x , y , z ) ] G(x,y,z)=\exp[F(x,y,z)] G(x,y,z)=exp[F(x,y,z)] 。只需对 F ( x , y , z ) F(x,y,z) F(x,y,z) 求导,即可得递推式。观察发现,对 y y y 求导会得到比较简单的形式。
∂ F ( x , y , z ) ∂ y = ∑ i , j , k ( 2 i + 2 k + j − 1 j − 1 ) ( i + k i ) 2 k x i y j − 1 z k − 1 = ∑ i , k ( i + k i ) x i ( 2 z ) k ( 1 − y ) 2 i + 2 k + 1 − 1 =  ⁣ =  ⁣ =  ⁣ = λ : = x + 2 z ∑ i λ i ( 1 − y ) 2 i + 1 − 1 = 1 1 − y [ 1 − λ ( 1 − y ) 2 ] − 1 − 1 = y − y 2 + λ 1 − λ − 2 y + y 2 \begin{aligned} {\partial F(x,y,z)\over\partial y} &=\sum_{i,j,k}{2i{+}2k{+}j{-}1\choose j{-}1}{i{+}k\choose i}2^kx^iy^{j-1}z^k-1\\ &=\sum_{i,k}{i{+}k\choose i}\frac{x^i(2z)^k}{(1{-}y)^{2i+2k+1}}-1\\ &\overset{\lambda:=x+2z}{=\!=\!=\!=}\sum_{i}{\lambda^i\over (1{-}y)^{2i+1}}-1\\ &={1\over 1{-}y}\left[1-\frac{\lambda}{(1{-}y)^2}\right]^{-1}-1\\ &=\frac{y-y^2+\lambda}{1-\lambda-2y+y^2} \end{aligned} yF(x,y,z)=i,j,k(j12i+2k+j1)(ii+k)2kxiyj1zk1=i,k(ii+k)(1y)2i+2k+1xi(2z)k1====λ:=x+2zi(1y)2i+1λi1=1y1[1(1y)2λ]11=1λ2y+y2yy2+λ

我们知道 ∂ G ( x , y , z ) ∂ y = ∂ F ( x , y , z ) ∂ y ⋅ G ( x , y , z ) \frac{\partial G(x,y,z)}{\partial y}=\frac{\partial F(x,y,z)}{\partial y}\cdot G(x,y,z) yG(x,y,z)=yF(x,y,z)G(x,y,z) 。用 v i , j v_{i,j} vi,j 表示 y i y^i yi 的系数中 λ j \lambda^j λj 项(这个说法不严谨,意会即可)的系数。对比系数可知
i ( v i , j − v i , j − 1 ) − 2 ( i − 1 ) v i − 1 , j + ( i − 2 ) v i − 2 , j = v i − 1 , j − 1 + v i − 2 , j − v i − 3 , j    ⟹    v i , j = v i , j − 1 + 1 i [ v i − 1 , j − 1 + ( 3 − i ) v i − 2 , j − v i − 3 , j + ( 2 i − 2 ) v i − 1 , j ] i(v_{i,j}-v_{i,j-1})-2(i{-}1)v_{i-1,j}+(i{-}2)v_{i-2,j}=v_{i-1,j-1}+v_{i-2,j}-v_{i-3,j}\\ \implies v_{i,j}=v_{i,j-1}+\frac{1}{i} \big[v_{i-1,j-1}+(3{-}i)v_{i-2,j}-v_{i-3,j}+(2i{-}2)v_{i-1,j}\big] i(vi,jvi,j1)2(i1)vi1,j+(i2)vi2,j=vi1,j1+vi2,jvi3,jvi,j=vi,j1+i1[vi1,j1+(3i)vi2,jvi3,j+(2i2)vi1,j]

也就是说,求出 y 0 y^0 y0 系数后,我们可以 O ( 1 ) \mathcal O(1) O(1) 得到每一项系数。这就是 O ( n 3 ) \mathcal O(n^3) O(n3) 的。

现在专注于求出初值 [ y 0 ] G ( x , y , z ) [y^0]G(x,y,z) [y0]G(x,y,z) 。相当于求出下面这个式子的 exp ⁡ \exp exp 结果。
∑ i k ≠ 0 ( i + k i ) x i ( 2 z ) k 2 ( i + k ) − x 2 \sum_{ik\ne 0}{i{+}k\choose i}\frac{x^i(2z)^k}{2(i{+}k)}-\frac{x}{2} ik=0(ii+k)2(i+k)xi(2z)k2x

忽略 − x 2 -\frac{x}{2} 2x 后,作变量代换 λ = x + 2 z \lambda=x+2z λ=x+2z,就是一元生成函数的 exp ⁡ \exp exp,你想怎么做就怎么做。然后乘 e − x 2 = ∑ i ⩾ 0 ( − x ) i 2 i ⋅ i ! e^{-\frac{x}{2}}=\sum_{i\geqslant 0}\frac{(-x)^i}{2^i\cdot i!} e2x=i02ii!(x)i 是很简单的。这一部分的时间复杂度也是 O ( n 3 ) \mathcal O(n^3) O(n3)

得到了这样的容斥值,容斥系数呢?可以发现是个简单的二维背包, O ( n 3 ) \mathcal O(n^3) O(n3) 暴力求出即可。

代码

#include <cstdio> // megalomaniac JZM yydJUNK!!!
#include <iostream> // Almighty XJX yyds!!!
#include <algorithm> // decent XYX yydLONELY!!!
#include <cstring> // Pet DDG yydOLDGOD!!!
#include <cctype> // oracle: ZXY yydBUSlut!!!
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
	return a*f;
}

const int MAXN = 505, MOD = 998244353;
inline int modAdd(int x, const int &y){
	return (x += y) >= MOD ? x-MOD : x;
}

int jc[MAXN], inv[MAXN], comb[MAXN][MAXN];
void prepare(const int &n){
	jc[0] = jc[1] = inv[1] = 1; rep(i,2,n){
		inv[i] = int(llong(MOD-MOD/i)*inv[MOD%i]%MOD);
		jc[i] = int(llong(jc[i-1])*i%MOD);
	}
	rep(i,**comb=1,n) rep(j,*comb[i]=1,i)
		comb[i][j] = modAdd(comb[i-1][j-1],comb[i-1][j]);
}

int deg[MAXN], pack[MAXN][MAXN];
int dp[MAXN][MAXN][MAXN]; ///< order: y, x, z
int main(){
	int n = readint(); prepare(n);
	{ // initialization
		static int xjx[MAXN]; xjx[0] = 1, xjx[1] = inv[2];
		rep(i,***dp=1,n){ // skip that
			xjx[i+1] = int(llong(i<<1|1)*xjx[i]%MOD*inv[2]%MOD*inv[i+1]%MOD);
			rep(j,0,i) dp[0][j][i-j] = int(llong(comb[i][j])*xjx[i]%MOD);
		}
		drep(i,n-1,0) for(int j=i+1,v=1; j<=n; ++j){
			v = int(llong(MOD-v)*inv[j-i]%MOD*inv[2]%MOD);
			rep(k,0,(n-j)>>1) dp[0][j][k] = int((
				dp[0][j][k]+llong(v)*dp[0][i][k])%MOD);
		}
	}
	rep(i,1,n) rep(j,0,n-i) rep(k,0,(n-i-j)>>1){
		int &w = dp[i][j][k] = j ? dp[i-1][j-1][k] : 0;
		if(k) w = modAdd(w,dp[i-1][j][k-1]);
		if(i >= 2){ // likely to be true
			w = int((w+llong((i-1)<<1)*dp[i-1][j][k]
				+llong(3+MOD-i)*dp[i-2][j][k])%MOD);
			if(i != 2) w = modAdd(w,MOD-dp[i-3][j][k]);
		}
		w = int(llong(w)*inv[i]%MOD); // get value
		if(j) w = modAdd(w,dp[i][j-1][k]);
		if(k) w = modAdd(w,dp[i][j][k-1]);
	}
	rep(i,0,n) rep(j,0,n-i) rep(k,0,(n-i-j)>>1)
		dp[i][j][k] = int(llong(dp[i][j][k])*jc[i]
			%MOD*jc[j]%MOD*jc[k]%MOD*jc[j+k]%MOD);
	rep(i,pack[0][0]=1,n) ++ deg[readint()];
	int siz = 0; ///< size of package
	for(int *d=deg+1; d!=deg+n+1; ++d) if(*d){
		const llong one = MOD-(*d); // *d == 1
		const llong two = (*d)*((*d)-1);
		drep(i,siz,0) drep(j,siz-i,0){
			pack[i+1][j] = int((pack[i+1][j]+one*pack[i][j])%MOD);
			pack[i][j+1] = int((pack[i][j+1]+two*pack[i][j])%MOD);
		}
		++ siz; // enlarge by one
	}
	int ans = 0;
	rep(i,0,siz) drep(j,std::min(siz-i,(n-i)>>1),0)
		ans = int((ans+llong(pack[i][j])*dp[i][n-i-(j<<1)][j])%MOD);
	printf("%d\n",ans);
	return 0;
}

后记

题解有一个 n n n 元生成函数的做法。看上去是 “没得技巧,全是硬算”。我只贴个图片吧。

可怖生成函数做法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值