题目
你真的认为选课是那么容易的事吗?HYSBZ的ZY同志告诉你,原来选课也会让人产生一种想要回到火星的感觉。假设你的一周有n天,那么ZY编写的选课系统就会给你n堂课。但是该系统不允许在星期i和星期i+1的时候选第i堂课,也不允许你在星期n和星期一的时候选第n堂课。然后连你自己也搞不清哪种选课方案合法,哪种选课不合法了。你只想知道,你到底有多少种合法的选课方案。
分析
声明一下,参考了题解。
我们定义(W_k)表示至少有k节课选错的方案数,
求出这个,用容斥原理就很容易求出正确选课方案数量。
那怎么求(W_k)呢?
现在总共 n 堂课分别记为 1,2,...n,它们可放的天数可以表示为(1,2)(2,3)(3,4)...(n,1)现在我们把括号去掉即得到一个数列 1,2,2,3,3,4,....n,1,现在我们从里面取出 K 个数,分别表示 k 堂课所在的天数,现在只要求出满足这个条件的取法数就可以了。
但是,我们不能再同一天选两节课,那么第(i*2-1和第i*2)个数是不可以同时取的,
接着,我们不能重复取一节课,那么第(i*2和第i*2+1)个数是不可以同时取的(当然,最后一个“1”和最前面的“1”也是不可以同时取的)。
也就是说,对于一个环,求从其中选取k个不相邻顶点得方案数:(C_{2n-k-1}^{k-1}*dfrac{2n}{k})
证明:对于任意一个顶点A,先取A,然后再从不和A相邻的n-3个其他顶点中取k-1个不相邻顶点,显然可得到符合定理要求的组合,这个组合的个数为C((n-3)-(k-1)+1,k-1)=C(n-k-1,k-1)。一共有n个顶点,而且在每个组合中有k个元素,即可完成证明。
然后其余的随便选,
所以(W_{k}=C_{2n-k-1}^{k-1}*dfrac{2n}{k}*(n-k)!)
THEN?容斥。
据说有种更强大的方法:这道提示有递推式滴!!!
但方法实在是太神奇了,根本就搞不懂。+_+
#include <cmath>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
const long long maxlongint=2147483647;
const long long mo=1000000007;
using namespace std;
long long jc[210000],ans,n,ny[210000],nn[210000];
long long mi(long long x,long long y)
{
long long sum=1;
while(y)
{
if(y&1) sum=sum*x%mo;
x=x*x%mo;
y/=2;
}
return sum;
}
long long C(long long m,long long n)
{
return jc[m]*nn[m-n]%mo*nn[n]%mo;
}
long long W(long long k)
{
return n*2*ny[n*2-k]%mo*C(n*2-k,k)%mo*jc[n-k]%mo;
}
int main()
{
for(long long i=0;i<=200005;i++)
ny[i]=mi(i,mo-2);
jc[0]=1;
nn[0]=1;
for(long long i=1;i<=200005;i++)
{
jc[i]=jc[i-1]*i%mo;
nn[i]=mi(jc[i],mo-2);
}
while(scanf("%lld",&n)!=EOF)
{
if(n==1)
{
printf("0
");
continue;
}
ans=jc[n];
for(long long i=1;i<=n;i++)
{
if(i%2)
ans=(ans-W(i)+mo)%mo;
else
ans=(ans+W(i))%mo;
}
printf("%lld
",ans);
}
}