Newcoder 83 D.队列重排(dp+组合数学)

190 篇文章 1 订阅
186 篇文章 0 订阅

Description

n ( n ≤ 500000 ) n(n\le 500000) n(n500000) 个人排成一列,把他们解散后重排,使得"重排后前方" 跟"原排列前方" 一样的人不超过 k ( k &lt; n ) k(k&lt;n) k(k<n) 个,问有几种方法数,答案请 m o d ( 1 0 9 + 7 ) mod (10^9+7) mod(109+7) 输出。

举例来说,有五个人编号为 1 1 1 5 5 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个人和重排后的第 1 1 1个人一定不会是「“重排后前方” 跟 “原排列前方” 一样的人」。

Input

输入只有一行,有两个正整数 n , k n,k n,k,满足 1 ≤ k &lt; n ≤ 500000 1\le k&lt;n\le 500000 1k<n500000

Output

请输出一行包含一个小于$10^9+7 $的非负整数,表示总共的方法数 m o d ( 1 0 9 + 7 ) mod (10^9+7) mod(109+7)

Sample Input

3 1

Sample Output

5

Solution

a n a_n an n n n个人重排后前方的人均不是原排列前方人的方案数,假设起初排位为 1 , . . . , n 1,...,n 1,...,n,考虑 1 1 1的作用

1. 1. 1. 1 1 1不再任意一对 i , i + 1 i,i+1 i,i+1之间,那么需要 2 2 2~ n n n n − 1 n-1 n1个人重排后前方的人均不是原排列前方的人,方案数为 a n − 1 a_{n-1} an1,对于这其中的每一种方案,只要 1 1 1不在 2 2 2前方即可,故 1 1 1 n − 1 n-1 n1个位置可以放

2. 2. 2. 1 1 1在某对 i , i + 1 i,i+1 i,i+1之间,选择一个 i i i方案数为 n − 2 n-2 n2,之后把 i , 1 , i + 1 i,1,i+1 i,1,i+1看作一个元素 i + 1 i+1 i+1,问题转化为 n − 2 n-2 n2个人重排后每个人前方的人均不是原排列前方的人,方案数 a n − 2 a_{n-2} an2

综上有 a n = ( n − 1 ) ⋅ a n − 1 + ( n − 2 ) ⋅ a n − 2 a_n=(n-1)\cdot a_{n-1}+(n-2)\cdot a_{n-2} an=(n1)an1+(n2)an2,线性预处理 a n a_n an即可

之后考虑原问题,假设重排后有 x x x个人前方的人和原排列前方的人相同,从 n − 1 n-1 n1个有前方人的人中选出这 x x x个人方案数 C n − 1 x C_{n-1}^x Cn1x,选出后,把这 x x x对看作 x x x个元素,问题转化为 n − x n-x nx个人重排后每个人前方的人均不是原排列前方人的方案数,即 a n − x a_{n-x} anx,故答案为 ∑ x = 0 k C n − 1 x ⋅ a n − x \sum\limits_{x=0}^kC_{n-1}^x\cdot a_{n-x} x=0kCn1xanx

Code

#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=500005;
#define mod 1000000007
int add(int x,int y)
{
	x+=y;
	if(x>=mod)x-=mod;
	return x;
}
int mul(int x,int y)
{
	ll z=1ll*x*y;
	return z-z/mod*mod;
}
int n,k,a[maxn],inv[maxn],fact[maxn];
void init(int n=5e5)
{
	inv[1]=1;
	for(int i=2;i<=n;i++)inv[i]=mul(mod-mod/i,inv[mod%i]);
	inv[0]=1;
	for(int i=1;i<=n;i++)inv[i]=mul(inv[i-1],inv[i]);
	fact[0]=1;
	for(int i=1;i<=n;i++)fact[i]=mul(i,fact[i-1]);
}
int C(int n,int m)
{
	return mul(fact[n],mul(inv[m],inv[n-m]));
}
int main()
{
	init();
	scanf("%d%d",&n,&k);
	a[1]=1,a[2]=1;
	for(int i=3;i<=n;i++)a[i]=add(mul(i-1,a[i-1]),mul(i-2,a[i-2]));
	int ans=0;
	for(int i=0;i<=k;i++)ans=add(ans,mul(C(n-1,i),a[n-i]));
	printf("%d\n",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值