题目:
https://ac.nowcoder.com/acm/problem/15614
有 n ( n ≤ 500000 ) n(n≤500000) n(n≤500000)个人排成一列,把他们解散后重排,使得"重排后前方" 跟"原排列前方" 一样的人不超过 k ( k < n ) k(k<n) k(k<n)个,问有几种方法数,答案请 m o d ( 1 0 9 + 7 ) mod(10^9+7) mod(109+7)输出。举例来说,有五个人编号为 1 ∼ 5 1\sim5 1∼5间的整数,最初的排列由前至后依序为 1 , 2 , 3 , 4 , 5 1, 2, 3, 4, 5 1,2,3,4,5,重排列后顺序由前至后变为 1 , 3 , 4 , 2 , 5 1, 3, 4, 2, 5 1,3,4,2,5,其中只有编号为 4 4 4的人,“原排列前方” 跟"重排后前方" 都是编号为 3 3 3的人,故"重排后前方" 跟"原排列前方" 一样的人只有 1 1 1人。原排列的第 1 1 1个人一定没有。
思路:
设 f ( i ) f(i) f(i)表示"重排后前方" 跟"原排列前方" 一样的人有 i i i个的方案数。刚开始有 n n n个连通块,就是 1 ∼ n 1\sim n 1∼n之间没有限制,如果编号为 j ( 2 ≤ j ) j(2\le j) j(2≤j)的人重排之后前面还是 j − 1 j-1 j−1,则 j j j和 j − 1 j-1 j−1一定要绑定在一起,则连通块个数减 1 1 1。所以 f ( i ) f(i) f(i)的时候有 n − i n-i n−i个连通块,那么现在就还得重排这 n − i n-i n−i个连通块,保证没有"重排后前方" 跟"原排列前方" 一样(因为连通块里面以及保证了有 i i i个)。
设 h ( i ) h(i) h(i)表示 i i i个连通块不存在"重排后前方" 跟"原排列前方" 一样的方案数。然后考虑递推,假设已经放好了前 i − 1 i-1 i−1个,这 i − 1 i-1 i−1个满足不存在"重排后前方" 跟"原排列前方" 一样。那么最后一个连通块可以有 i − 1 i-1 i−1个位置放。但是还有另外一种情况就是 i − 1 i-1 i−1个当中存在一个"重排后前方" 跟"原排列前方" 一样(有 i − 2 i-2 i−2种方式),然后最后一个插入进去破坏掉。这样相当于前 i − 1 i-1 i−1个只有 i − 2 i-2 i−2个连通块。所以有递推式
h ( i ) = ( i − 1 ) h ( i − 1 ) + ( i − 2 ) h ( i − 2 ) h(i)=(i-1)h(i-1)+(i-2)h(i-2) h(i)=(i−1)h(i−1)+(i−2)h(i−2)
则
f ( i ) = ( n i ) h ( n − i ) a n s = ∑ i = 0 k f ( i ) \begin{aligned} f(i)&={n\choose i}h(n-i)\\ ans&=\sum_{i=0}^{k}f(i) \end{aligned} f(i)ans=(in)h(n−i)=i=0∑kf(i)
#include<cstdio>
const int N=5e5+88;
const int P=1e9+7;
int ksm(int x,int k){
int ans=1;
for(;k;k>>=1,x=1LL*x*x%P) if(k&1) ans=1LL*ans*x%P;
return ans;
}
int ans,n,k,C[N],a[N];
int main(){
scanf("%d%d",&n,&k);
a[1]=a[2]=1;
for(int i=3;i<=n;++i) a[i]=(1LL*(i-1)*a[i-1]%P+1LL*(i-2)*a[i-2]%P)%P;
C[0]=1;
for(int i=1;i<n;++i) C[i]=1LL*C[i-1]*(n-i)%P*ksm(i,P-2)%P;
for(int i=0;i<=k;++i) ans=(ans+1LL*C[i]*a[n-i]%P)%P;
printf("%d\n",ans);
}