题目
题目背景
你以为这是
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
a→b 和
b
→
a
b\to a
b→a 可以同时存在。
现在,规定有向边 i → p i ( i ∈ S ) i\to p_i\;(i\in S) i→pi(i∈S) 不存在,请你求出这样的 2 -regular 2\text{-regular} 2-regular 图数量。
数据范围与约定
n
⩽
500
n\leqslant 500
n⩽500 且
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×(b−12a+2c+b−1)×(b−1)! 。我们惊讶地发现
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+b−1)!(c=0∨a+b⩾2)
看上去 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+j−1)!(ii+k)2kxiyjzk−2x−y
答案就是
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}
∂y∂F(x,y,z)=i,j,k∑(j−12i+2k+j−1)(ii+k)2kxiyj−1zk−1=i,k∑(ii+k)(1−y)2i+2k+1xi(2z)k−1====λ:=x+2zi∑(1−y)2i+1λi−1=1−y1[1−(1−y)2λ]−1−1=1−λ−2y+y2y−y2+λ
我们知道
∂
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)
∂y∂G(x,y,z)=∂y∂F(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,j−vi,j−1)−2(i−1)vi−1,j+(i−2)vi−2,j=vi−1,j−1+vi−2,j−vi−3,j⟹vi,j=vi,j−1+i1[vi−1,j−1+(3−i)vi−2,j−vi−3,j+(2i−2)vi−1,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)k−2x
忽略 − 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!} e−2x=∑i⩾02i⋅i!(−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 元生成函数的做法。看上去是 “没得技巧,全是硬算”。我只贴个图片吧。