[组合数][前缀和][快速幂][容斥原理] Jzoj P3466 选课
题解
- 先给大家送上一句话:水法真神奇,暴力出奇迹!!!
- 大爷说这题可以打表找规律!!!大爷说这题可以打表找规律!!!大爷说这题可以打表找规律!!!
- 重要的事请说三遍
- 规律: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\n"); 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\n",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\n",(f[n]-f[n-1]+1000000007)%1000000007); 9 }