1. 程式人生 > >bzoj 4517: [Sdoi2016]排列計數【容斥原理+組合數學】

bzoj 4517: [Sdoi2016]排列計數【容斥原理+組合數學】

沒有 原理 getchar() display del d+ getchar esp const

第一個一眼就A的容斥題!
這個顯然是容斥的經典問題------錯排,首先考慮沒有固定的情況,設\( D_n \)為\( n \)個數字的錯排方案數。
\[ D_n=n!-\sum_{t=1}^{n}(-1)^{t-1}\sum_{i_1<i_2<...<i_t}(n-t)! \]
\[ D_n=n!+\sum_{t=1}^{n}(-1)^tC_{n}^{t}(n-t)! \]
\[ D_n=n!+\sum(-1)^t\frac{n!}{t!} \]
推到這一步就可以了,然後觀察數據範圍顯然是要線性預處理,於是計算遞推式:
\[ D_{(n+1)}=(n+1)!+\sum_{t=1}^{n+1}(-1)^t\frac{(n+1)!}{t!} \]


\[ D_{(n+1)}=(n+1)!+(n+1)\sum_{t=1}^{n+1}(-1)^t\frac{n!}{t!} \]
\[ D_{(n+1)}=(n+1)!+(n+1)(\sum_{t=1}^{n}(-1)^t\frac{n!}{t!}+(-1)^{n+1}\frac{n!}{(n+1)!}) \]
\[ D_{(n+1)}=(n+1)!+(n+1)\sum_{t=1}^{n}(-1)^t\frac{n!}{t!}+(-1)^{n+1} \]
\[ D_{(n+1)}=(n+1)(n!+(n+1)\sum_{t=1}^{n}(-1)^t\frac{n!}{t!})+(-1)^{n+1} \]

\[ D_{(n+1)}=(n+1)D_n+(-1)^{n+1} \]
\[ D_i=i*D_{i-1}+(-1)^i \]
然後考慮有\( m \)的限制,就相當於\( m \)個數字固定,剩下\( n-m \)個數字錯排,直接從預處理的\( D \)裏面查即可,最後乘上選出\( m \)個固定位的方案數,對組合數預處理階乘、逆元。由此可得答案為:
\[ ans=D_{(n-m)}*C_{n}^{m} \]
這東西推起來真刺激

#include<iostream>
#include<cstdio>
using namespace std;
const long long
N=1000005,mod=1e9+7; long long T,n,m,inv[N],fac[N],cp[N]; int read() { int r=0; char p=getchar(); while(p>‘9‘||p<‘0‘) p=getchar(); while(p>=‘0‘&&p<=‘9‘) { r=r*10+p-48; p=getchar(); } return r; } long long ksm(long long a,long long b) { long long r=1ll; while(b) { if(b&1) r=r*a%mod; a=a*a%mod; b>>=1; } return r; } long long C(long long n,long long m) { return fac[n]*inv[n-m]%mod*inv[m]%mod; } int main() { fac[0]=1; for(long long i=1;i<=N-5;i++) fac[i]=fac[i-1]*i%mod; inv[N-5]=ksm(fac[N-5],mod-2); for(long long i=N-6;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod; cp[0]=1;//這裏的cp數組即是上文提到的D(cuopai 23333) for(long long i=1;i<=N-5;i++) cp[i]=(i*cp[i-1]+((i&1)?-1:1))%mod; T=read(); while(T--) { n=read(),m=read(); printf("%lld\n",(cp[n-m]*C(n,m)%mod+mod)%mod); } return 0; }

bzoj 4517: [Sdoi2016]排列計數【容斥原理+組合數學】