[JZOJ3466] 选课

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 项和第 2i1 项(也就是一个括号中的两个数)不可以同时选,因为一个括号代表一门课,一门课不可以上两天

两个条件合并,就是

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

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

先考虑一个序列的情况

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

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

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

所以这样选的方案数是 Ck2Nk+1

考虑环

破环为链

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

于是就变成了 Ck2Nk

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

所以多乘了 2Nk ,这一部分要除掉

剩下的 Nk 门课可以乱选了。

W(k)=Ck2Nk×2N2Nk×(Nk)!

化简公式

W(k)=(2Nk1)!(Nk)!2Nk!(2N2k)!

注意要预处理阶乘和逆元

Code

#include
#include
#include
#include
#include
#include
#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);
        }
    }
}

你可能感兴趣的:(————逆元,————快速幂,————容斥原理,————排列组合)