题目
思路
考场思路
将对应关系写出来,分别是 1 R , 1 B , 2 R , 2 B , … , n R , n B 1R,1B,2R,2B,\dots,nR,nB 1R,1B,2R,2B,…,nR,nB 的对应值(数字为编号,字母为颜色)。
选出 x x x 个值是出现了两次的,然后塞到里面去。但是可能两点之间有两条边。
容斥,假设有 r r r 种数字是出现于同数字的不同颜色。那么方案数为
( n x ) ∑ r = 0 x ( − 1 ) r ( x r ) n ! ( n − r ) ! [ ( n − r ) ! ( n − x ) ! ] 2 {n\choose x}\sum_{r=0}^{x}(-1)^{r}{x\choose r}\frac{n!}{(n-r)!}\left[\frac{(n-r)!}{(n-x)!}\right]^2 (xn)r=0∑x(−1)r(rx)(n−r)!n![(n−x)!(n−r)!]2
= ∑ r = 0 x ( − 1 ) r ( n ! ) 2 ( n − r ) ! [ ( n − x ) ! ] 3 r ! ( x − r ) ! =\sum_{r=0}^{x}\frac{(-1)^r(n!)^2(n-r)!}{[(n-x)!]^3r!(x-r)!} =r=0∑x[(n−x)!]3r!(x−r)!(−1)r(n!)2(n−r)!
= ( n ! ) 2 [ ( n − x ) ! ] 2 ∑ r = 0 x ( n − r n − x ) ⋅ ( − 1 ) r r ! =\frac{(n!)^2}{[(n-x)!]^2}\sum_{r=0}^{x}{n-r\choose n-x}\cdot\frac{(-1)^r}{r!} =[(n−x)!]2(n!)2r=0∑x(n−xn−r)⋅r!(−1)r
只出现了一次的又怎么办呢?剩下 n − x n-x n−x 种数字和 2 n − 2 x 2n-2x 2n−2x 个位置,可以随便放。方案应当是
f ( n − x ) = ∑ i = 0 n − x ( n − x i ) ( 2 n − 2 x ) ! ( 2 n − 2 x − i ) ! f(n-x)=\sum_{i=0}^{n-x}{n-x\choose i}\frac{(2n-2x)!}{(2n-2x-i)!} f(n−x)=i=0∑n−x(in−x)(2n−2x−i)!(2n−2x)!
换元可知,答案应当为
∑ x = 0 n ∑ r = 0 n − x ∑ i = 0 x ( n ! ) 2 ( x ! ) 2 ( n − r x ) ( − 1 ) r r ! ( x i ) ( 2 x ) ! ( 2 x − i ) ! \sum_{x=0}^{n}\sum_{r=0}^{n-x}\sum_{i=0}^{x}\frac{(n!)^2}{(x!)^2}{n-r\choose x}\frac{(-1)^r}{r!}{x\choose i}\frac{(2x)!}{(2x-i)!} x=0∑nr=0∑n−xi=0∑x(x!)2(n!)2(xn−r)r!(−1)r(ix)(2x−i)!(2x)!
= ∑ r = 0 n ( − 1 ) r r ! ∑ i = 0 n − r ( n − r i ) ∑ x = i n − r ( n − r − i x − i ) ( n ! ) 2 ( x ! ) 2 ⋅ ( 2 x ) ! ( 2 x − i ) ! =\sum_{r=0}^{n}\frac{(-1)^r}{r!}\sum_{i=0}^{n-r}{n-r\choose i}\sum_{x=i}^{n-r}{n-r-i\choose x-i}\frac{(n!)^2}{(x!)^2}\cdot\frac{(2x)!}{(2x-i)!} =r=0∑nr!(−1)ri=0∑n−r(in−r)x=i∑n−r(x−in−r−i)(x!)2(n!)2⋅(2x−i)!(2x)!
然后暴毙。
正确思路
假设有 x x x 条红边、 y y y 条蓝边,则答案似乎应为
[ ( n x ) ( n y ) ] 2 x ! y ! \left[{n\choose x}{n\choose y}\right]^2x!y! [(xn)(yn)]2x!y!
但是需要去掉某两个点之间有两条边的情况。设有 r r r 个这样的点,则
a n s = ∑ r = 0 n ( − 1 ) r [ ( n r ) ( n − r x ) ( n − r y ) ] 2 ⋅ r ! ⋅ x ! ⋅ y ! ans=\sum_{r=0}^{n}(-1)^r\left[{n\choose r}{n-r\choose x}{n-r\choose y}\right]^2\cdot r!\cdot x!\cdot y! ans=r=0∑n(−1)r[(rn)(xn−r)(yn−r)]2⋅r!⋅x!⋅y!
为了简化,去掉了两个求和符号。
令 f ( m ) = ∑ x = 0 m ( m x ) 2 x ! f(m)=\sum_{x=0}^{m}{m\choose x}^2x! f(m)=x=0∑m(xm)2x!
则 ∑ r = 0 n ( − 1 ) r ⋅ r ! ⋅ ( n r ) 2 f ( n − r ) 2 \sum_{r=0}^{n}(-1)^r\cdot r!\cdot{n\choose r}^2f(n-r)^2 r=0∑n(−1)r⋅r!⋅(rn)2f(n−r)2
f ( m ) f(m) f(m) 的现实意义为:在 m × m m\times m m×m 的矩阵上放置任意多个 r o o k \tt rook rook ,使其互相不可达。
考虑如何递推计算。考虑第一行上有一个 r o o k \tt rook rook ,则去掉其所在的行列,剩下的就是 f ( m − 1 ) f(m-1) f(m−1) 。
问题来了,第一行没有 r o o k \tt rook rook 怎么办?难道不能转化成子问题了吗?
但是剩下的是一个 m × ( m − 1 ) m\times(m-1) m×(m−1) 的矩阵,最多只有 m − 1 m-1 m−1 个 r o o k \tt rook rook ,也就最多只会占用 ( m − 1 ) × ( m − 1 ) (m-1)\times(m-1) (m−1)×(m−1) 的矩阵。所以还是 f ( m − 1 ) f(m-1) f(m−1) 的情况,但是要插入一个空的列,所以是 m ⋅ f ( m − 1 ) m\cdot f(m-1) m⋅f(m−1) 。
然而事情并不妙,因为 f ( m − 1 ) f(m-1) f(m−1) 未必填满了。有多少个空列,就会被计算多少次。所以推不动。
不妨引入辅助数组 g ( m ) g(m) g(m) 表示填 m × ( m − 1 ) m\times (m-1) m×(m−1) 矩阵的情况。那么转移必须枚举第一行,这样就可以回到 f f f 或者 g g g 的状态。如果有 r o o k \tt rook rook ,显然是 g ( m − 1 ) g(m-1) g(m−1) ,否则是 f ( m − 1 ) f(m-1) f(m−1) 。至此,我们可以写出
{ f ( x ) = g ( x ) + x ⋅ f ( x − 1 ) g ( x ) = ( x − 1 ) ⋅ g ( x − 1 ) + f ( x − 1 ) \begin{cases} f(x)=g(x)+x\cdot f(x-1)\\ g(x)=(x-1)\cdot g(x-1)+f(x-1) \end{cases} {f(x)=g(x)+x⋅f(x−1)g(x)=(x−1)⋅g(x−1)+f(x−1)
完成了 O ( n ) \mathcal O(n) O(n) 递推,剩下一切好搞。
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int x){
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
inline int qkpow(int_ b,int q,int m){
int ans = 1;
for(; q; q>>=1,b=b*b%m)
if(q&1) ans = ans*b%m;
return ans;
}
const int MaxN = 10000005;
const int Mod = 1e9+7;
int inv[MaxN], f[MaxN], g[MaxN];
int main(){
int n = readint();
inv[1] = 1;
for(int i=2; i<=n; ++i)
inv[i] = (0ll+Mod-Mod/i)*inv[Mod%i]%Mod;
f[0] = 1, g[0] = 0;
for(int i=1; i<=n; ++i){
g[i] = ((i-1ll)*g[i-1]+f[i-1])%Mod;
f[i] = (g[i]+1ll*i*f[i-1])%Mod;
}
int_ c = 1; // C(n,r)
int_ jc = 1; // r!
int ans = 0;
for(int r=0; r<=n; ++r){
int_ t = c*f[n-r]%Mod;
t = t*t%Mod*jc%Mod;
if(r&1) t = Mod-t;
ans = (ans+t)%Mod;
c = c*(n-r)%Mod*inv[r+1]%Mod;
jc = jc*(r+1)%Mod;
}
printf("%d\n",ans);
return 0;
}
后记
还有一种办法,就是 继续考虑第一列有没有 r o o k \tt rook rook 。所以“第一列有”、“第一行有”、“左上角有”、“第一行第一列都没有”四种情况都会转移到 f ( m − 1 ) f(m-1) f(m−1) 。加在一起是 2 m ⋅ f ( m − 1 ) 2m\cdot f(m-1) 2m⋅f(m−1) 。
如果第一行第一列都有,就会被重复计算。类似上面的思路,这是 f ( m − 2 ) f(m-2) f(m−2) 的情况。第一行第一列各选一个的方案数是 ( m − 1 ) 2 (m-1)^2 (m−1)2 ,所以最终递推式为
f ( m ) = 2 m ⋅ f ( m − 1 ) + ( m − 1 ) 2 f ( m − 2 ) f(m)=2m\cdot f(m-1)+(m-1)^2f(m-2) f(m)=2m⋅f(m−1)+(m−1)2f(m−2)
所以这是怎么想到的,我对着一堆阶乘推了好久。