Description

你真的认为选课是那么容易的事吗?HYSBZ的ZY同志告诉你,原来选课也会让人产生一种想要回到火星的感觉。
假设你的一周有n天,那么ZY编写的选课系统就会给你n堂课。但是该系统不允许在星期i和星期i+1的时候选第i堂课,也不允许你在星期n和星期一的时候选第n堂课。然后连你自己也搞不清哪种选课方案合法,哪种选课不合法了。你只想知道,你到底有多少种合法的选课方案。
答案mod 109+7

Solution

这是一道。。。。额好吧,这是一道结论题

许多人说过,正难则反十分显然我并没有贯彻落实这一思想

So⋯

设W(k)表示至少有k门课是不合法的方案数
搞出来W(k)以后就是裸的容斥原理了

然而,W怎么求呢

把每堂课不合法的天数写出来(也就是W中要选的)
(1,2)(2,3)(3,4)(4,5)⋯(N,1)

把括号去掉排成一个长为2N序列
然后我们要从中选出k个括号,其中每个括号选一个数,代表这k节课的日期
并且要满足

  • 第2i项和第2i+1项(也就是相邻两个括号相邻的两个数)不可以同时选,因为同一天不能有两节课
  • 第2i项和第2i−1项(也就是一个括号中的两个数)不可以同时选,因为一个括号代表一门课,一门课不可以上两天

两个条件合并,就是

  • 相邻两项不能同时选!!

给你2N个点的环(因为首尾相接),求不能选相邻两个点的选k个点的方案数

先考虑一个序列的情况

我们选了k个点,那么要使两个点之间至少有一个点没选

我们是不是可以想,把这些点删去,是不是就可以任意选了?

原问题就变成了在2N−k+1个点中选k个,然后把剩下k−1个在每两个之间放进去就合法了。

所以这样选的方案数是Ck2N−k+1

考虑环

破环为链

然后我们显然可以像在序列中那样,在第一个选前面的和最后一个选后面这段中的删掉一个

于是就变成了Ck2N−k

然后破环的方式有2N种,所以还要乘个2N
完了,吗?
因为乘了2N以后,每种选法中的2N−k个点都有可能成为破环的那个点

所以多乘了2N−k,这一部分要除掉

剩下的N−k门课可以乱选了。

W(k)=Ck2N−k×2N2N−k×(N−k)!

化简公式

W(k)=(2N−k−1)!(N−k)!2Nk!(2N−2k)!

注意要预处理阶乘和逆元

Code

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define mo 1000000007
#define LL long long
using namespace std;
int n;
LL js[200001],ny[200001],ny1[200001];
LL ksm(LL k,LL n)
{
LL s;
if (n==1) return(k);
s=ksm(k,n/2);
if (n%2==0)return(s*s%mo);
else return(s*s%mo*k%mo);
}
int main()
{
freopen("select.in","r",stdin);
int i;
js[0]=1;
LL x,y;
fo(i,1,200000)
{
js[i]=(i*js[i-1])%mo;
ny[i]=ksm(js[i],mo-2);
}
ny[0]=1;
while (scanf("%d",&n)!=EOF)
{
if (n<=2) printf("0\n");
else
{
LL ans=js[n],j=-1;
fo(i,1,n)
{
ans=(ans+j*js[n-i]*2*n%mo%mo*js[2*n-i-1]%mo*ny[i]%mo*ny[2*n-2*i]%mo+mo)%mo;
j=-j;
}
printf("%lld\n",ans);
}
}
}