Description
你真的认为选课是那么容易的事吗?HYSBZ的ZY同志告诉你,原来选课也会让人产生一种想要回到火星的感觉。假设你的一周有n天,那么ZY编写的选课系统就会给你n堂课。但是该系统不允许在星期i和星期i+1的时候选第i堂课,也不允许你在星期n和星期一的时候选第n堂课。然后连你自己也搞不清哪种选课方案合法,哪种选课不合法了。你只想知道,你到底有多少种合法的选课方案。
Solution
直接求并不好求,我们考虑求出选错 k 节课的方案数。
我们先把每天不能选的课表示出来:
我们把里面的元素当成一个序列,就是 1,2,2,3,3,⋯n,n,1
那么这个序列相当于一个环,那么选错
k
节课的方案相当于从这个环中选出
那么问题来了,怎么选呢?
因为可以破环为链,我们考虑在序列上的做法。
我们把所有的课当成球,选出
k
个球后剩下的球当成隔板,那么隔板隔成了
那么在圆环上,我们破掉环,考虑不合法情况(首尾都选),
2
~
那么总方案数就是:
Ckn−k+1−Ck−2n−k−1=nn−kCkn−k
由于只确定了选错
k
节课的方案数,所以还要乘上
那么 Wk=nn−kCkn−k(n−k)!
然后容斥一下就好了。
PS:此篇写的比较简略,后面可能会补充。
Code
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
#define N 200001
#define ll long long
#define mo 1000000007
using namespace std;
ll jc[N],zz[N],z[N];
ll c(int m,int n)
{
return jc[m]*zz[n]%mo*zz[m-n]%mo;
}
ll pow(ll m,ll n)
{
ll b=1;
while(n)
{
if(n%2==1) b=b*m%mo;
n/=2;
m=m*m%mo;
}
return b;
}
int main()
{
int n;
jc[0]=1;
z[0]=1;
zz[0]=1;
fo(i,1,N-1)
{
jc[i]=jc[i-1]*i%mo;
zz[i]=pow(jc[i],mo-2);
z[i]=pow(i,mo-2);
}
while(scanf("%d",&n)!=EOF)
{
if(n==1)
{
printf("0\n");
continue;
}
ll ans=jc[n];
fo(i,1,n)
{
ll p=c(n*2-i,i)*n*2%mo*z[n*2-i]%mo*jc[n-i]%mo;
if(i%2==0) ans=(ans+p)%mo;
else ans=(ans+mo-p)%mo;
}
printf("%d\n",ans);
}
}