[美团 CodeM 初赛 Round A]二分图染色

280 篇文章 1 订阅
220 篇文章 2 订阅

题目

传送门 to LOJ

思路

考场思路

将对应关系写出来,分别是 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=0x(1)r(rx)(nr)!n![(nx)!(nr)!]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=0x[(nx)!]3r!(xr)!(1)r(n!)2(nr)!

= ( 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!} =[(nx)!]2(n!)2r=0x(nxnr)r!(1)r

只出现了一次的又怎么办呢?剩下 n − x n-x nx 种数字和 2 n − 2 x 2n-2x 2n2x 个位置,可以随便放。方案应当是

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(nx)=i=0nx(inx)(2n2xi)!(2n2x)!

换元可知,答案应当为

∑ 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=0nr=0nxi=0x(x!)2(n!)2(xnr)r!(1)r(ix)(2xi)!(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=0nr!(1)ri=0nr(inr)x=inr(xinri)(x!)2(n!)2(2xi)!(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=0n(1)r[(rn)(xnr)(ynr)]2r!x!y!

为了简化,去掉了两个求和符号。

f ( m ) = ∑ x = 0 m ( m x ) 2 x ! f(m)=\sum_{x=0}^{m}{m\choose x}^2x! f(m)=x=0m(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=0n(1)rr!(rn)2f(nr)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(m1)

问题来了,第一行没有 r o o k \tt rook rook 怎么办?难道不能转化成子问题了吗?

但是剩下的是一个 m × ( m − 1 ) m\times(m-1) m×(m1) 的矩阵,最多只有 m − 1 m-1 m1 r o o k \tt rook rook ,也就最多只会占用 ( m − 1 ) × ( m − 1 ) (m-1)\times(m-1) (m1)×(m1) 的矩阵。所以还是 f ( m − 1 ) f(m-1) f(m1) 的情况,但是要插入一个空的列,所以是 m ⋅ f ( m − 1 ) m\cdot f(m-1) mf(m1)

然而事情并不妙,因为 f ( m − 1 ) f(m-1) f(m1) 未必填满了。有多少个空列,就会被计算多少次。所以推不动。

不妨引入辅助数组 g ( m ) g(m) g(m) 表示填 m × ( m − 1 ) m\times (m-1) m×(m1) 矩阵的情况。那么转移必须枚举第一行,这样就可以回到 f f f 或者 g g g 的状态。如果有 r o o k \tt rook rook ,显然是 g ( m − 1 ) g(m-1) g(m1) ,否则是 f ( m − 1 ) f(m-1) f(m1) 。至此,我们可以写出

{ 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)+xf(x1)g(x)=(x1)g(x1)+f(x1)

完成了 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(m1) 。加在一起是 2 m ⋅ f ( m − 1 ) 2m\cdot f(m-1) 2mf(m1)

如果第一行第一列都有,就会被重复计算。类似上面的思路,这是 f ( m − 2 ) f(m-2) f(m2) 的情况。第一行第一列各选一个的方案数是 ( m − 1 ) 2 (m-1)^2 (m1)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)=2mf(m1)+(m1)2f(m2)

所以这是怎么想到的,我对着一堆阶乘推了好久。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值