队列重排

题目:
https://ac.nowcoder.com/acm/problem/15614

n ( n ≤ 500000 ) n(n≤500000) n(n500000)个人排成一列,把他们解散后重排,使得"重排后前方" 跟"原排列前方" 一样的人不超过 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 15间的整数,最初的排列由前至后依序为 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 1n之间没有限制,如果编号为 j ( 2 ≤ j ) j(2\le j) j(2j)的人重排之后前面还是 j − 1 j-1 j1,则 j j j j − 1 j-1 j1一定要绑定在一起,则连通块个数减 1 1 1。所以 f ( i ) f(i) f(i)的时候有 n − i n-i ni个连通块,所以有公式, n = n= n=相邻的个数 + + +连通块的个数。那么现在就还得重排这 n − i n-i ni个连通块,保证连通块没有"重排后前方" 跟"原排列前方" 一样(因为连通块里面已经保证了有 i i i个相邻,那么连通块之间就不能相邻)。

h ( i ) h(i) h(i)表示 i i i个连通块不存在"重排后前方" 跟"原排列前方" 一样的方案数。然后考虑递推,假设已经放好了前 i − 1 i-1 i1个,这 i − 1 i-1 i1个满足不存在"重排后前方" 跟"原排列前方" 一样。那么最后一个连通块可以有 i − 1 i-1 i1个位置放。但是还有另外一种情况就是 i − 1 i-1 i1个当中存在一个"重排后前方" 跟"原排列前方" 一样(有 i − 2 i-2 i2种方式),然后最后一个插入进去破坏掉。这样相当于前 i − 1 i-1 i1个只有 i − 2 i-2 i2个连通块。所以有递推式
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)=(i1)h(i1)+(i2)h(i2)

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(ni)=i=0kf(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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值