1. 程式人生 > 實用技巧 >[Noip2017]組合數問題

[Noip2017]組合數問題

[Noip2017]組合數問題

一.前言

​ 組合數學沒學好阿巴阿巴……題目連結

二.思路

​ 首先給一個結論:楊輝三角和組合數的關係是密不可分的。這兩個就是一個東西其實。先來康康一個很顯而易見的數學公式。

\[C_m^n=C_{m}^{n-1}+C_{m-1}^{n-1} \]

大概,還是很容易理解的,吧……稍作解釋就是。

在 n 個數中選 m 個做組合的情況可以從 在前 n-1 個數中就選出 m 個和 在前 n-1個數中選出 m-1 個轉移過來(後一種是第 n 個塞進去)

然後稍加觀察,若是把 n 當作行, m 當作列的話,就是楊輝三角。換而言之,\(C^i_j\) 為楊輝三角的第 i 行,第 j 列的值。

然後我們再看問題,求的是 k 的倍數的個數,也就是 \(\%k=0\) 的個數,這裡將詢問離線,預處理直接\(O(1)\)解決詢問。所以預處理一個基於楊輝三角的字首和陣列 \(sum[n][m]\) 表示答案。轉移方程為

\[sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1] \]

然後加一個當前值是不是 \(\%k=0\) 判斷就ok。

​ 關於轉移方程,可以具體手推,這裡給出一個抽象理解。(今天手比較抖)

框住的表示這一部分的字首和,現在是在求紅框。灰色是重複部分。

但是這是常規的楊輝三角,計算機裡面的楊輝三角,稍微有點點不同,是個方的,所以畫圖為

大概是這種感覺,然後可以發現籃框右下角是空點,實際上空點的值應該是空點左邊的值,為了方便計算就有一個\(sum[i][i+1]=sum[i][i]\),後面推就沒有問題了。

三.CODE

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
using namespace std;
int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
	return res*f;
}
int t,k,n,m;
int f[2005][2005],sum[2005][2005];
int main(){
	t=read();k=read();
	for(int i=0;i<=2004;++i)f[i][i]=f[i][0]=1;
	for(int i=1;i<=2004;++i){
		for(int j=1;j<=i;++j){
			f[i][j]=(f[i-1][j]+f[i-1][j-1])%k;//一直膜就對了
			sum[i][j]=(sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]);
			if(!f[i][j])sum[i][j]++;
		}
		sum[i][i+1]=sum[i][i];//比較關鍵的一步
	}
	while(t--){
		n=read();m=read();
		cout<<sum[n][min(n,m)]<<endl;
	}
	return 0;
}