【JZOJ3466】选课

Description

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

Solution

直接求并不好求,我们考虑求出选错 k 节课的方案数。

我们先把每天不能选的课表示出来:(1,2)(2,3)(n1,n)(n,1)

我们把里面的元素当成一个序列,就是 1,2,2,3,3,n,n,1

那么这个序列相当于一个环,那么选错 k 节课的方案相当于从这个环中选出k个互不相邻的数。

那么问题来了,怎么选呢?

因为可以破环为链,我们考虑在序列上的做法。

我们把所有的课当成球,选出 k 个球后剩下的球当成隔板,那么隔板隔成了nk+1个房间,那么现在从这些房间中选出 k 个房间和与从序列上选k个互不相邻的元素是一一对应的。所以我们从序列上取的方案数有: Cknk+1

那么在圆环上,我们破掉环,考虑不合法情况(首尾都选), 2 ~n1选的方案数由上化简可得: Ck2nk1

那么总方案数就是:

Cknk+1Ck2nk1=nnkCknk

由于只确定了选错 k 节课的方案数,所以还要乘上(nk)!

那么 Wk=nnkCknk(nk)!

然后容斥一下就好了。

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);
    }
}
posted @ 2016-07-01 20:40  sadstone  阅读(39)  评论(0编辑  收藏  举报