Description
你真的认为选课是那么容易的事吗?HYSBZ的ZY同志告诉你,原来选课也会让人产生一种想要回到火星的感觉。假设你的一周有n天,那么ZY编写的选课系统就会给你n堂课。但是该系统不允许在星期i和星期i+1的时候选第i堂课,也不允许你在星期n和星期一的时候选第n堂课。然后连你自己也搞不清哪种选课方案合法,哪种选课不合法了。你只想知道,你到底有多少种合法的选课方案。
对于第i组数据,n<=10*i, 1<=i<=3
对于100%的数据:1<=n<=100000
Analysis
本来还想暴力然后高斯消元找递推式的但是没时间打了。
看了题解才知道递推式鬼一样难找。
但是,本题还有另一种不需要递推的方法,即组合数方法。
转化
首先,正难则反,考虑恰好有
k
门课选错的方案数。如果我们能解决这个问题,就可以直接上容斥原理。
这个问题的解决方法极为巧妙,以我的智商是很难想到的。
首先,把
拆括号,变成一个序列,
1,2,2,3,3,4,.....n,1
首先,同一天不能选两门课,所以第
2i−1,2i
个不能同时选到。
又由于第
2i,2i+1
个相同,同一门课不能被同时两天选,所以也不能选到。
所以恰好选错
k
门课的方案数就是在这个序列里选出
而且这个序列实质上是一个圆环。所以,问题变成了求圆排列上选
k
个互不相邻的元素的方案数。
这个问题比较难,我们从简单想起。
如果不是圆环而就是一个序列,怎么算?
方法一
如图,黑色球为选的,白色为不选的。
我们让黑色球吃掉它右边的第一个球,最后一个黑色球不吃,序列就变成了这样。
这里,元素共有
为什么呢?反过来,所有的长度为
n−k+1
的序列,选出
k
个黑色球,都可以让那
这两个是一一对应的。所以,其方案数也是相等的。
方法二
还有一种来自Alan_cty的方法,也能推出来。
把黑球拿出来排成一排,每个黑球的前面后面都恰好有一个盒子,显然总共有
k+1
个盒子。除了最左和最右的盒子,中间的那些盒子都至少放一个球来使这些黑球不相邻,所以这是限制放置数的组合数,可以参考一下【SDOI2013方程】这道题。
方法三
正难则反!(这是一个非常好的思想)
我们不妨把选出一些球看成插入一些球,这样就能用组合数了嘛。
问题也就转化为,原本有
n−k
个球,有
n−k+1
个空,现在要插入
k
个球,而且两个球不能同时插入一个空。
这样,方案数显而易见地就是
这个方法是最容易理解,最简单的方法。
推广
在圆环上,可以把它剪开,破坏成链。显然,满足链上的不相邻方案的且首尾不能同时选的方案数就是环上的方案数。
首尾若都选,则第二位和倒数第二位都必不选,那么就是中间的元素不相邻的方案数,即
n−4
个元素选出
k−2
个元素。
那么方案数就是
Ckn−k+1−Ck−2(n−4)−(k−2)+1
,化简,得
nn−k∗Ckn−k
。
当然,确定了这
k
门课,其他
于是就可以容斥了。
O(n∗T)
,
T
<script type="math/tex" id="MathJax-Element-1019">T</script>为数据组数。
实现要注意优化常数。
Code
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=200010,mo=int(1e9)+7;
ll fac[N],ny[N],_ny[N];
ll qmi(ll x,int n)
{
ll t=1;
for(;n;n>>=1)
{
if(n&1) t=t*x%mo;
x=x*x%mo;
}
return t;
}
ll C(int m,int n)
{
return fac[m]*ny[n]%mo*ny[m-n]%mo;
}
int main()
{
int n;
fac[0]=ny[0]=1;
fo(i,1,N-10) fac[i]=fac[i-1]*i%mo,ny[i]=qmi(fac[i],mo-2),_ny[i]=qmi(i,mo-2);
while(scanf("%d",&n)!=EOF)
{
if(n==1)
{
printf("0\n");
continue;
}
ll t,ans=0;
fo(k,0,n)
{
t=fac[n-k]*2*n%mo*_ny[2*n-k]%mo*C(2*n-k,k)%mo;
if(k&1) ans=(ans-t+mo)%mo;
else ans=(ans+t)%mo;
}
printf("%lld\n",ans);
}
return 0;
}