1. 程式人生 > >Newcoder 83 D.佇列重排(dp+組合數學)

Newcoder 83 D.佇列重排(dp+組合數學)

Description

n(n500000)n(n\le 500000) 個人排成一列,把他們解散後重排,使得"重排後前方" 跟"原排列前方" 一樣的人不超過k(k<n)k(k<n) 個,問有幾種方法數,答案請mod(109+7)mod (10^9+7) 輸出。

舉例來說,有五個人編號為1155 間的整數,最初的排列由前至後依序為1,2,3,4,51, 2, 3, 4, 5,重排列後順序由前至後變為1,3,4,2,51, 3, 4, 2, 5,其中只有編號為44 的人,“原排列前方” 跟"重排後前方" 都是編號為3

3 的人,故"重排後前方" 跟"原排列前方" 一樣的人只有11人。

原排列的第11個人和重排後的第11個人一定不會是「“重排後前方” 跟 “原排列前方” 一樣的人」。

Input

輸入只有一行,有兩個正整數n,kn,k,滿足1k<n5000001\le k<n\le 500000

Output

請輸出一行包含一個小於$10^9+7 $的非負整數,表示總共的方法數 mod(109+7)mod (10^9+7)

Sample Input

3 1

Sample Output

5

Solution

ana_nnn個人重排後前方的人均不是原排列前方人的方案數,假設起初排位為1

,...,n1,...,n,考慮11的作用

1.1.11不再任意一對i,i+1i,i+1之間,那麼需要22~nnn1n-1個人重排後前方的人均不是原排列前方的人,方案數為an1a_{n-1},對於這其中的每一種方案,只要11不在22前方即可,故11n1n-1個位置可以放

2.2.11在某對i,i+1i,i+1之間,選擇一個ii方案數為n2n-2,之後把i,1,i+1i,1,i+1看作一個元素i+1i+1,問題轉化為n2n-2個人重排後每個人前方的人均不是原排列前方的人,方案數a

n2a_{n-2}

綜上有an=(n1)an1+(n2)an2a_n=(n-1)\cdot a_{n-1}+(n-2)\cdot a_{n-2},線性預處理ana_n即可

之後考慮原問題,假設重排後有xx個人前方的人和原排列前方的人相同,從n1n-1個有前方人的人中選出這xx個人方案數Cn1xC_{n-1}^x,選出後,把這xx對看作xx個元素,問題轉化為nxn-x個人重排後每個人前方的人均不是原排列前方人的方案數,即anxa_{n-x},故答案為x=0kCn1xanx\sum\limits_{x=0}^kC_{n-1}^x\cdot a_{n-x}

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;
}