1. 程式人生 > 實用技巧 >【Luogu P4071】[SDOI2016]排列計數

【Luogu P4071】[SDOI2016]排列計數

題目大意:

求有多少種 \(1\)\(n\) 的排列 \(a\),滿足序列恰好有 \(m\) 個位置 \(i\),使得 \(a_i=i\),答案對 \(10^{9}+7\)

正文:

可以先列個表:

再從題目意思出發,若 \(m=0\),即沒有一個數字在自己位置上,那就是錯位排列。錯位排列的遞推式是 \(f_i=(i-1)(f_{i-1}+f{i-2})\)。而我們在列表的過程中會發現,特殊情況除外,除在自己位置上的 \(m\) 個數,其它的數進行錯位排列。也就是說 最終答案 = 確定排列的數的總值 * 其它數錯位排列。

問題來了,確定排列的數的總值是多少?我們不妨舉舉例,設 \(n=4,m=2\)

,那麼就有 \(\{1,2,x,x\},\{1,x,3,x\},\{1,x,x,4\},\{x,2,3,x\},\{x,2,x,4\},\{x,x,3,4\}\).總共就有 \(C_{n}^{m}\)

程式碼:

ll _pow(ll a, int b){
    a %= p;
	ll ans = 1;
	for(; b; b >>= 1, a = a * a % p)
		if(b & 1)
			ans = ans * a % p;
    return ans;
}
void init()
{
	prod[0] = 1;
	for (register int i = 1; i <= 1000000; i++)
		prod[i] = (prod[i - 1] * i) % p,
		inv[i] = _pow(prod[i], p - 2);
	a[2] = 1;
	for (register int i = 3; i <= 1000000; i++)
		a[i] = (i - 1) * ((a[i - 1] + a[i - 2]) % p) % p;
}

int main()
{
	int t;
	init();
	for (scanf("%d", &t); t--;)
	{
		scanf("%d%d", &n, &m);
		if(m == 0)
		{
			printf("%lld\n", a[n]);
			continue;
		}
		if(n == m){puts("1");continue;}
		if(n - 1 == m){puts("0");continue;}  //圖表中的幾個特殊情況
		printf("%lld\n", (prod[n] * inv[m] % p * inv[n-m] % p) * a[n-m] % p); //括號內的是組合數,a陣列是錯位排列個數
	}
	return 0;
}