[组合数][前缀和][快速幂][容斥原理] Jzoj P3466 选课

Description

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

Input

有多组数据,请读到文件末结束。

对于每组数据仅一行,1个正整数 n。

Output

对于每组输出只有一行,1个非负整数,为选课方案数量 mod (10^9+7). 
 

Sample Input

2
4

Sample Output

0
2
【样例解释】
  对于样例二:
  周一上第二堂课,周二上第三堂课,周三上第四堂课,周四上第一堂课;
  周一上第三堂课,周二上第四堂课,周三上第一堂课,周四上第二堂课。
共2种选课方案。
 
 

Data Constraint

对于第i组数据,n<=10*i, 1<=i<=3

对于100%的数据:1<=n<=100000

题解

  • 先给大家送上一句话:水法真神奇,暴力出奇迹!!!
  • 大爷说这题可以打表找规律!!!大爷说这题可以打表找规律!!!大爷说这题可以打表找规律!!!
  • 重要的事请说三遍
  • 规律:f[i]=(f[i-1]+f[i-2])*(i-1)+f[i-3]
  • 以上是水法时间
  • 正解要来啦:
  • 对于这题,我们可以反向思维求错误的方案数
  • 容斥原理,假设有 1 堂课所在的天数是错误的情况数是 W(1)
  • 则每个错误的课程 i 可以放在 i 或 i+1 天,剩下的选课方法数是(n-1)!
  • 所以 w(1)=C(n,1)*2*(n-1)!
  • 那么对于 2 个以上的情况,假设有 k 堂课所在的天数是错误的情况数是 W(K)
  • 总共 n 堂课分别 记为 1,2,...n,它们可放的天数可以表示为(1,2)(2,3)(3,4)...(n,1)
  • 现在我们把括号去掉即得到一个数列 1,2,2,3,3,4,....n,1,
  • 现在 我们从里面取出 K 个数,分别表示 k 堂课所在的天数
  • 现在只要求出满足 这个条件的取法数就可以了
  • 定理:假定数n个顶点沿一圆周排列,则从其中选取k个不相邻顶点的方法 数是n/k*C(n-k-1,k-1)
  • 证明:对于任意一个顶点A,先取A,然后再从不和A相邻的n-3个其他顶点 中取k-1个不相邻顶点
  • 显然可得到符合定理要求的组合,这个组合的个数 为C((n-3)-(k-1)+1,k-1)=C(n-k-1,k-1)
  • 一共有n个顶点,而且在每个组 合中有k个元素,即可完成证明
  • 那么W(K)=(n-k)!*C(2n-k-1,k-1)*2n/k
  • 有人会问为什么要乘(n-k)!,因为我们只选了K堂错的课,剩余(n-k) 堂课可以随意选
  • W(K)确定以后就是最裸的容斥原理了

正解代码

 1 #include <cstdio>
 2 #include <iostream>
 3 using namespace std;
 4 const int mo=1000000007,maxn=200005;
 5 int n;
 6 long long f[maxn],ny[maxn];
 7 long long ksm(long long k,int t)
 8 {
 9     if (!t) return 1;
10     long long mid=ksm(k,t/2);
11     if (t&1) return mid*mid%mo*k%mo;
12     return mid*mid%mo;
13 }
14 long long get(int x) { return f[n-x]%mo*2*n%mo*(f[2*n-x-1]*(ny[x]*ny[2*(n-x)]%mo)%mo)%mo; }
15 int main()
16 {
17     f[0]=f[1]=1; ny[0]=ny[1]=1;
18     for (int i=2;i<maxn;i++) f[i]=f[i-1]*i%mo,ny[i]=ksm(f[i],mo-2);
19     while (scanf("%d",&n)!=EOF)
20     {
21         if (n==1) { printf("0
"); continue; }
22         long long ans=f[n];
23         for (int i=1;i<=n;i++)
24         {
25             long long k=get(i);
26             if (i%2==1) ans=(ans-k+mo)%mo; else ans=(ans+k)%mo;
27         }
28         printf("%lld
",ans);
29     }
30     return 0;
31 }

水法代码

1 #include<cstdio>
2 using namespace std;
3 int n;long long f[100001];
4 int main()
5 {
6     f[3]=1,f[4]=3;
7     for (int i=5;i<=100000;i++) f[i]=((f[i-1]+f[i-2])*(i-1)+f[i-3])%1000000007;
8     while (scanf("%d",&n)!=EOF) printf("%lld
",(f[n]-f[n-1]+1000000007)%1000000007);
9 }
原文地址:https://www.cnblogs.com/Comfortable/p/9338038.html