题目解析
考试的时候我是在按照点在想,可以把点分成四类:只有绿色边,有一条红色边,有一条蓝色边,有一条红色边和一条蓝色边(后三种其余都是绿色边),然后把(n)分解成四个整数的和,再分别计数。但是一个点的情况可能会影响到其它与之有边相连的点,所以不好做。
不要怕,直接来
还是从边的角度来考虑。
由于绿色没有影响,所以我们把它当作不染色。先算只有一种颜色的染色方案。设(f(n))表示对(n)个点的二分图的染色方案,由于同一个点只能染一条边,先枚举一个有染色边的点数(i),然后就相当于从这(n)个点中选(i)个点出来,连到另一边的点去,而另一边的点也不能同时连两条有色边,所以另一边也要选(i)个点出来。注意到我一边选点的时候不在乎顺序,但是连到另一边的时候,另一边的点与这一边的点的对应情况不一样,那么连出来的边就不一样,所以另一边的点有顺序。
得到(f(n)=sum_{i=0}^nC_n^iA_n^i)
现在有两种颜色,如果直接算(f^2(n))会把一条边染上两种颜色,现在考虑一下怎么减去不合法的方案。可以用容斥原理,我们钦定一些边让它们被染上两种颜色,总方案数(=)钦定(0)条边被染两种颜色,其它随便(-)钦定(1)条边被染两种颜色,其它随便(+)钦定(2)条边被染两种颜色,其它随便(……)
具体而言,(ans=sum_{i=0}^n (-1)^i*C(n,i)*A(n,i)*f^2(n-i))
((-1)^i)是容斥系数,(C(n,i)*A(n,i))是钦定(i)条边出来染两种颜色,原理同上,(*f^2(n-i))是剩下的边随便染色。
组合数可以预处理,但是(f( i))要(n^2)求,现在,我们就有了一个(n^2)的优秀算法。
瓶颈在(f(i))的求法,我们考虑能不能不用通项公式,而是(O(n))递推出来。
我们考虑在(n-1)对点的外面再加上一对点会有哪些方案:
- 新加点的边不染色,方案数是(f(n-1))
- 新加点之间的边染色,方案数是(f(n-1))
- 新加点的其中一个与另一边原来的(n-1)个点之间的边选一条染色,方案数是((n-1)f(n-1))
- 同理,新加点的另一个与另一边原来的(n-1)个点之间的边选一条染色,方案数是((n-1)f(n-1))
- 上面四种情况的方案数加起来,我们发现(4,5)可能与原来的点存在冲突,也就是我其中一个点和另一边原来点连边的时候,那个原来点可能与另一个原来点已经有染色边。这个怎么解决呢?我们可以假设如果有冲突,就把冲突的点连给另外那个新增点。举个例子:(a,b)是新增点,(a)往对面的原来点(x)连边,而(x)又已经与原来点(y)有一条染色边,那么我们把(x-y)这条边改成(b-y)。这样改动之后,不会有冲突,但是(4,5)中有算重的方案,所以我们还要减去:(a)与对面原来(n-1)个点连边,且(b)与对面(n-1)个点连边的方案数(这其实是一个小容斥),为((n-1)^2*f[n-2])
然后得到递推关系(f(n)=2n*f(n-1)-(n-1)^2*f[n-2])
(因为我懒,没有画图XD,所以不太清楚的地方可以手动画图~
转化一下问题
棋盘问题可以转化为二分图来解决,那么这个二分图也可以转化为棋盘。
把二分图的(X)部放在(x)轴,(Y)部放在(y)轴,那么每一个格点就代表了一条边,我们对格点染色。
同种颜色的边不能在一个结点上,也就是同一行同一列只能有一个这个颜色,就是在棋盘上放车的问题。
同样地,先考虑一种车,可以得到(f(n)=sum_{i=0}^nC_n^iA_n^i),即在(n)行中选出(i)行放车,选出来之后它们的列可以顺次有(n,n-1,n-2...n-i+1)种选择,当然也可以理解成有顺序地选。(这个似乎比二分图染色直观一点
还是一样的容斥,钦定有(i)个两个颜色的车放在了同一个格点。(ans=sum_{i=0}^n (-1)^i*C(n,i)*A(n,i)*f^2(n-i))
同样的考虑递推式。从(f(n-1))到(f(n))相当于外面多了一圈格点共(2n-1)个出来。
我们枚举选择周围的一圈格点,然后删掉那一行一列,剩下的(n-1)行,(n-1)列化归成(f(n))
还有一种方案是外面这一圈一个都不选,所以一共有(2n)种,再乘上化归之后的(f (n-1))
那个,我之前想错了,我以为是外面这一圈是或者的关系,然后乘上里面的那个原来的(n-1)的棋盘,那这样的方案(如下图)就没有办法算:
但实际上是可以的,按照前面的正确思路理解,就是删掉选中的那一行那一列,剩下的最后一列还是在化归的方案里。
但是还是有算重的情况,就是下图的情况,它在行,列里面都算过,要减去。
(其实也可以想成之前的那种替换的想法,如果有冲突,就把冲突的点搬走,然后就相当于有了多出来的行列都有格子选的情况,但是这样会算重,就是一种方案可以是由行来算,冲突搬到列去,也可以是列来算,把冲突搬到行里去)
►Code View
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
using namespace std;
#define N 10000005
#define MOD 1000000007
#define INF 0x3f3f3f3f3f3f3f3f
#define LL long long
LL rd()
{
LL x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
return f*x;
}
int n;
LL fac[N],inv[N],f[N];
LL ksm(LL a,LL b)
{
LL res=1ll;
while(b)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
void Init()
{
fac[0]=1,inv[0]=1;
for(int i=1;i<=N-5;i++)
fac[i]=fac[i-1]*i%MOD;
inv[N-5]=ksm(fac[N-5],MOD-2);
for(int i=N-6;i>=1;i--)
inv[i]=inv[i+1]*(i+1)%MOD;
}
LL C(int a,int b)
{
return fac[a]*inv[a-b]%MOD*inv[b]%MOD;
}
LL A(int a,int b)
{
return fac[a]*inv[a-b]%MOD;
}
int main()
{
n=rd();
Init();
f[0]=1,f[1]=2;//边界 一条边 染/不染
for(int i=2;i<=n;i++)
f[i]=(2ll*i*f[i-1]%MOD-1ll*(i-1)*(i-1)%MOD*f[i-2]%MOD+MOD)%MOD;
//for(int i=2;i<=n;i++)
// printf("%lld
",f[i]);
LL ans=0;
for(int i=0;i<=n;i++)
{
LL res=C(n,i)*A(n,i)%MOD*f[n-i]%MOD*f[n-i]%MOD;
if(i&1) ans=(ans-res+MOD)%MOD;
else ans=(ans+res)%MOD;
}
printf("%lld
",ans);
return 0;
}
/*
f(n)=sum i=0->n C(n,i)*A(n,i)
ans= sum i=0->n (-1)^i*C(n,i)*A(n,i)*f(n-i)^2
f(n)=2n*f(n-1)-(n-1)^2*f(n-2)
*/