1. 程式人生 > >HIT ~ 1402~整數劃分問題(動態規劃 or 母函式)

HIT ~ 1402~整數劃分問題(動態規劃 or 母函式)



概念

編輯所謂整數劃分,是指把一個正整數n寫成為其中,  為正整數,並且  ;  為n的一個劃分。如果  中的最大值不超過m,即  ,則稱它屬於n的一個m劃分。[1]

求劃分個數

編輯

分析

這裡我們記n的m劃分的個數為  。例如,當n=4時,有5個劃分,即  ,  ,  ,  ,  。注意:  和  被認為是同一個劃分。根據n和m的關係,考慮一下幾種情況:(一)當  時,無論m的值為多少  ,只有一種劃分,即  。(二)當  時,無論n的值為多少,只有一種劃分,即n個1,  。(三)當  時,根據劃分中是否包含n,可以分為以下兩種情況:(1)劃分中包含n的情況,只有一個,即  。(2)劃分中不包含n的情況,這時劃分中最大的數字也一定比n小,即n的所有 
 劃分。因此  。(四)當  時,由於劃分中不可能出現負數,因此就相當於  。(五)當  時,根據劃分中是否包含最大值m,可以分為以下兩種情況:(1)劃分中包含m的情況,即  ,其中  的和為n-m,因此這種情況下為  。(2)劃分中不包含m的情況,則劃分中所有值都比m小,即n的  劃分,個數為  。因此  。綜上所述:

遞迴程式碼:

#include<bits/stdc++.h>
using namespace std;  
int equation(int n,int m)
{
    if(n==1||m==1)
        return (1);
    else if(n<m)
        return equation(n,n);
    else if(n==m)
        return 1+equation(n,n-1);
    else
        return equation(n-m,m)+equation(n,m-1);
}
int main()
{
    int equation(int n,int m);
    int n,m;
    printf("Please input 'n'(0<n<100):");
    scanf("%d",&n);
    printf("Please input 'm'(0<m<=n):");
    scanf("%d",&m);
    printf("quantity:%d\n",equation(n,m));
}

以上內容來自百度百科

整數劃分問題


整數劃分是一個經典的問題。希望這道題會對你的組合數學的解題能力有所幫助。

Input

每組輸入是兩個整數n和k。(1 <= n <= 50, 1 <= k <= n)

Output

對於每組輸入,請輸出六行。

第一行: 將n劃分成若干正整數之和的劃分數。
第二行: 將n劃分成k個正整數之和的劃分數。
第三行: 將n劃分成最大數不超過k的劃分數。
第四行: 將n劃分成若干奇正整數之和的劃分數。
第五行: 將n劃分成若干不同整數之和的劃分數。
第六行: 列印一個空行。

Sample Input
5 2
Sample Output
7
2
3
3
3

Hint:

  1. 將5劃分成若干正整數之和的劃分為: 5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1
  2. 將5劃分成2個正整數之和的劃分為: 3+2, 4+1
  3. 將5劃分成最大數不超過2的劃分為: 1+1+1+1+1, 1+1+1+2, 1+2+2
  4. 將5劃分成若干奇正整數之和的劃分為: 5, 1+1+3, 1+1+1+1+1
  5. 將5劃分成若干不同整數之和的劃分為: 5, 1+4, 2+3

思路:

1.將n劃分成若干正整數之和的劃分數(一定要完全理解!!!)

dp[n][k]表示n這個數劃分為最大值不超過k的劃分數

初始化條件:n==1||k==1時,dp[n][k]=1,因為最大k=1的時候只有一種分法就是n個數字都為1,或者n=1了,也只有一種分法就是一個1

還有dp[0][1~n]=1,因為當n=k時,且劃分中含有k的情況此時dp[n-k][k]應該為1而不是0,所以我們將dp[0][1~n]賦值為1

《1》當n>=k時,dp[n][k]由兩種狀態可以得到,即n的劃分中有k和沒k

        ①dp[n-k][k],意為n的劃分中含有k

        ②dp[n][k-1],意為n的劃分中沒有k

此時dp[n][k]=dp[n-k][k]+dp[n][k-1]

《2》當n<k時,因為不存在負數所以dp[n][k]=dp[n][n];

2.將n劃分成k個正整數之和的劃分數(n個蘋果分在k個盤子裡面,盤子不允許為空,問有多少種分法)

dp[n][k]表示n這個數分為k個數字的劃分數-------n個蘋果分在k個盤子裡

初始化條件:當k=1的時候,dp[n][k]=1,只有一個盤子了只有一種情況

《1》n>=k時,dp[n][k]由兩種狀態得到,即n的劃分中有1和沒1

        ①dp[n-1][k-1],意為n的劃分中含有1--------有一個盤子只放一個蘋果,以後就不考慮這個盤子了

        ②dp[n-k][k],意為n的劃分中沒有1--------每個盤子都放一個蘋果

《2》n<k時,dp[n][]k]=0--------n小於k,n個蘋果放k個盤子,盤子還不允許為空,所以這種情況為0 

3.將n劃分為最大數不超過k的劃分數(跟第一問基本一模一樣,POJ-1664跟這個問題一樣)

4.將n劃分成若干奇正整數之和的劃分數

dp[n][k]表示n這個數劃分為最大值不超過k的劃分數

初始化條件:dp[0~n][1]的值為1,任何數劃分為不超過1的數都只有一種情況,dp[0][奇數]=1(原因同第一個問題)

《1》k為奇數

①n>=k時,dp[n][k]由兩種狀態來,一dp[n-k][k],意為n的劃分中含有k,二dp[n][k-1],意為n的劃分中沒有k

②n<k時,dp[n][k]=dp[n][n],因為不存在負數;

《2》k為偶數,dp[n][k]=dp[n][k-1]

5.將n劃分為若干不同正整數之和的劃分數

dp[n][k]表示n這個數劃分為最大值不超過k且不同的劃分數

初始化條件:n==1時,dp[n][k]=1,因為最n=1了,也只有一種分法就是一個1

還有dp[0][1~n]=1,因為當n=k時,且劃分中含有k的情況此時dp[n-k][k]應該為1而不是0,所以我們將dp[0][1~n]賦值為1

《1》當n>=k時,dp[n][k]由兩種狀態可以得到,即n的劃分中有k和沒k

        ①dp[n-k][k-1],意為n的劃分中含有k,則再劃分k就不能再用了

        ②dp[n][k-1],意為n的劃分中沒有k

此時dp[n][k]=dp[n-k][k]+dp[n][k-1]

《2》當n<k時,因為不存在負數所以dp[n][k]=dp[n][n];

程式碼如下:
//#include <bits/stdc++.h>
#include<iostream>
#include<cstring>
using namespace std;  
const int maxn=55;
int n,k,dp[maxn][maxn];
int a()//將n劃分成若干正整數之和的劃分數。
{
	memset(dp,0,sizeof(dp)); dp[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//i=0的時候這裡會把dp[0][1~n]初始化為1  
								   //這樣後面在遞推的時候會直接把dp[1][1~n]和dp[1~n][1]的情況賦值為1 
		}
	}
	return dp[n][n];
}
int b()//將n劃分成k個正整數之和的劃分數。
{
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(j==1) dp[i][j]=1;//j=1的時候這裡會把dp[1~n][1]初始化為1 
			else if(i>=j) dp[i][j]=dp[i-j][j]+dp[i-1][j-1];
		}
	}
	return dp[n][k];
}
int c()//將n劃分為若干最大值不超過k的正整數之和      除了返回值和a()不一樣其他都一樣 
{
	memset(dp,0,sizeof(dp)); dp[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//i=0的時候這裡會把dp[0][1~n]初始化為1 
								   //這樣在遞推的時候會直接把dp[1][1~n]和dp[1~n][1]的情況賦值為1 
		}
	}
	return dp[n][k];
}
int d()// 將n劃分成若干奇正整數之和的劃分數。
{
	memset(dp,0,sizeof(dp));
	for(int i=0;i<=n;i++)//初始化dp陣列 
	{
        dp[i][1]=1;
        if(i&1) dp[0][i]=1;
    }
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(j&1)
			{
				if(i>=j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
				else dp[i][j]=dp[i][i];
			}
			else dp[i][j]=dp[i][j-1];
		}
	}
	return dp[n][n];
}
int e()//將n劃分成若干不同整數之和的劃分數。
{
	memset(dp,0,sizeof(dp));dp[0][0]=1;
	for(int i=0;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(i>=j) dp[i][j]=dp[i-j][j-1]+dp[i][j-1];
			else dp[i][j]=dp[i][i];//i=0的時候這裡會把dp[0][1~n]初始化為1
								   //這樣在遞推的時候會直接把dp[1][1~n]的情況賦值為1  
		}
	}
	return dp[n][n];
}
int main()  
{
	while(~scanf("%d%d",&n,&k))
	{
		printf("%d\n%d\n%d\n%d\n%d\n\n",a(),b(),c(),d(),e());
	}
    return 0;
}

母函式法:

問題:將n劃分為若干個正整數的劃分數

我們可以構建一個函式:

g(x)=(x0+x1+x2+x3+...+xn)(x0+x2+x4+x6+...)(x0+x3+x6+x9+...)...(x0+xn)

第i個括號$(1+x^i+x^{2i}+x^{3i} · ·)$選擇的元素代表了數字i在我們最終的劃分中出現的次數

我們不要管X是什麼因為我們用不到他,我們只用這個函式的指數和係數;這個多項式展開後X^n次方的係數就是n的劃分數。

對於這樣一個式子X^3是如何得到的呢,從第一個括號中挑X^3,其他括號挑X^0,或者從第一個括號挑X^1,第二個括號挑X^2,其他括號挑X^0,再或者從第三個括號挑X^3,

所以X^3的係數為三,正好是X^3的劃分數。

我們可以看一下4的劃分為:①1+1+1+1,②1+1+2,③3+1,④2+2,⑤4共五種情況,(也正好是這個構造多項式的X^4的係數,大家可以手推算一下有助於理解)

①從第一個括號中挑X^4代表四個1,其他括號全挑X^0

②從第一個括號中挑X^2代表倆個1,第二個括號中挑X^2代表一個2,其他括號全挑X^0

③從第一個括號中挑X^1代表一個1,第三個括號中挑X^3代表一個三

④從第二個括號中挑X^4代表兩個2,其他括號全挑X^0

⑤從第四個括號中挑X^4代表一個4,其他括號全挑X^0

在這個多項式中X^4也就是這五種方法得到的。

例題OpenJ_Bailian-4117     

題意:將n劃分為若干個正整數的劃分數

程式碼:

#include <bits/stdc++.h> 
using namespace std;
const int maxn=55;
int a[maxn],b[maxn],c[maxn];
void Poly()///多項式乘法 
{
	memset(c,0,sizeof(c));
	for(int i=0;i<maxn;i++)
	{
		for(int j=0;j<maxn-i;j++)
		{
			c[i+j]+=(a[i]*b[j]);
		}
	} 
}
int main()
{
	for(int i=0;i<maxn;i++) a[i]=1;// 第一個括號g(x,1)=x^0+x^1+x^2+x^3...
	for(int i=2;i<maxn;i++) 
	{
		memset(b,0,sizeof(b));
		for(int j=0;j<maxn;j+=i) b[j]=1;// 第i個括號g(x,j)=x^(0*j)+x^(1*j)+x^(2*j)+x^(3*j)...
		Poly();
		memcpy(a,c,sizeof(c));//相乘結果從c複製到a 
	}
	int n;
	while(cin>>n)
	{
		cout<<a[n]<<endl;
	}
	return 0;
} 

例題UESTC-624          

網址:整數劃分

題意:將n劃分為若干個不相同的正整數

生成函式:

g(x)=(x0+x1)(x0+x2)(x0+x3)...(x0+xn)

程式碼:

#include <bits/stdc++.h> 
using namespace std;
const int maxn=1005;
const int MOD=19901014;
int a[maxn],b[maxn],c[maxn];
void Poly(int k)///多項式乘法 
{
	memset(c,0,sizeof(c));
	for(int i=0;i<maxn;i++)
	{
		for(int j=0;j<maxn-i;j+=k)
		{
			c[i+j]+=(a[i]*b[j])%MOD;
		}
	} 
}
int main()
{
	memset(a,0,sizeof(a));a[0]=a[1]=1;//第一個括號 g(x,1)=x^0+x^1
	for(int i=2;i<maxn;i++) 
	{
		memset(b,0,sizeof(b));
		b[0]=b[i]=1;// 第二個括號 g(x,j)=x^(0*j)+x^(1*j)
		Poly(i);
		memcpy(a,c,sizeof(c));//相乘結果從c複製到a 
	}
	int T,n;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);printf("%d\n",c[n]);
	}
	return 0;
} 

這裡再附一份我看不懂的程式碼:
#include <bits/stdc++.h> 
using namespace std;
const int maxn=1005;
const int MOD=19901014;
int c1[maxn],c2[maxn];
int main()
{
	c1[0]=1;c1[1]=1;
    for(int i=2;i<maxn;i++)
	{  
        for(int j=0;j<maxn;j++)
		{  
            c2[j]+=c1[j]%MOD;  
            if(j+i<maxn)  c2[i+j]+=c1[j]%MOD;
        }
        for(int j=0;j<maxn;j++)
		{
            c1[j]=c2[j];
            c2[j]=0;
        }
    }
	int T,n;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);printf("%d\n",c1[n]);
	}
	return 0;
} 


問題:將n劃分為若干個奇正整數之和的劃分數

生成函式:

g(x)=(x0+x1+x2+x3+...+xn)(x0+x3+x6+x9+...)(x0+x5+x10+x15+...)...
程式碼讀者自己練習把。

該問題還有些變種,如要求整數連續等等;
本人學識淺薄,如有錯誤,望大家不吝賜教;

相關推薦

HIT ~ 1402~整數劃分問題動態規劃 or 函式

概念編輯所謂整數劃分,是指把一個正整數n寫成為其中,  為正整數,並且  ;  為n的一個劃分。如果  中的最大值不超過m,即  ,則稱它屬於n的一個m劃分。[1]求劃分個數編輯分析這裡我們記n的m劃分的個數為  。例如,當n=4時,有5個劃分,即  ,  ,  ,  ,  

整數劃分動態規劃

經典問題。將正整數n表示成一系列正整數之和,n=n1+n2+..+nk  1. 將n劃分成不大於m的劃分法(多個整數可以相同) dp[n][m]= dp[n][m-1]+ dp[n-m][m]  dp[n][m]表示:整數 n 的劃分中,每個數不大於 m 的劃分數。

整數劃分問題---動態規劃、遞迴

第一: 將一個整數 n 劃分為 不超過m 組 的劃分數 如  n=4 m=3 輸出: 4 { 1+1+2=1+3=2+2=4} 思路:使用動態規劃: 定義狀態: dp[i][j] j的i劃分的組數 遞推:dp[i][j]=dp[i][j-i]+dp[i-1][j] ----

演算法訓練 數的劃分 動態規劃

 演算法訓練 數的劃分   時間限制:1.0s   記憶體限制:256.0MB 問題描述   將整數n分成k份,且每份不能為空,任意兩份不能相同(不考慮順序)。   例如:n=7,k

【BZOJ4872】分手是祝願動態規劃,數學期望

esp math map ostream pac mes ++i rac define 【BZOJ4872】分手是祝願(動態規劃,數學期望) 題面 BZOJ 題解 對於一個狀態,如何求解當前的最短步數? 從大到小枚舉,每次把最大的沒有關掉的燈關掉 暴力枚舉因數關就好 假設我

【BZOJ1415】【NOI2005】聰聰和可可動態規劃,數學期望

數學期望 class ios char for problem lin vector noi 【BZOJ1415】【NOI2005】聰聰和可可(動態規劃,數學期望) 題面 BZOJ 題解 先預處理出當可可在某個點,聰聰在某個點時 聰聰會往哪裏走 然後記憶化搜索一下就好了 #

【題解】 P1879 玉米田Corn Fields 動態規劃,狀態壓縮

bad sin 是否 editor infer nbsp 一行 als clas 題目描述 Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1

【BZOJ5299】【CQOI2018】解鎖屏幕動態規劃,狀態壓縮

++ src 規劃 希望 getch cstring online androi 形狀 【BZOJ5299】【CQOI2018】解鎖屏幕(動態規劃,狀態壓縮) 題面 BZOJ 洛谷 Description 使用過Android手機的同學一定對手勢解鎖屏幕不陌生。Androi

【BZOJ4654】【NOI2016】國王飲水記動態規劃,斜率優化

code 奇怪 while lib show ima double 優化 .com 【BZOJ4654】【NOI2016】國王飲水記(動態規劃,斜率優化) 題面 BZOJ 洛谷 題解 首先肯定是找性質。 明確一點,比\(h_1\)小的沒有任何意義。 所以我們按照\(h\)排

【BZOJ3203】保護出題人動態規劃,斜率優化

現在 bzoj3203 d+ while 我們 register 攻擊 nod http 【BZOJ3203】保護出題人(動態規劃,斜率優化) 題面 BZOJ 洛谷 題解 在最優情況下,肯定是存在某只僵屍在到達重點的那一瞬間將其打死 我們現在知道了每只僵屍到達終點的時間,因

【BZOJ1226】學校食堂動態規劃,狀態壓縮

食堂 有關 轉移 mem sizeof fin 狀壓 set lin 【BZOJ1226】學校食堂(動態規劃,狀態壓縮) 題面 BZOJ 洛谷 題解 發現\(b\)很小,意味著當前這個人最壞情況下也只有後面的一小部分人在他前面拿到飯。 所以整個結果的大致順序是不會變化的。

【BZOJ4455】小星星動態規劃,容斥

之間 lld algorithm std 還需要 tchar 一次 lin 還需 【BZOJ4455】小星星(動態規劃,容斥) 題面 BZOJ 洛谷 Uoj 題解 題意說簡單點就是給定一張\(n\)個點的圖和一棵\(n\)個點的樹,現在要讓圖和樹之間的點一一對應,並且如果樹

BZOJ2442 Usaco2011 Open修剪草坪動態規劃+單調隊列

getc math turn size 單調隊列 規劃 lin ret ios   顯然可以dp。顯然可以單調隊列優化一下。 #include<iostream> #include<cstdio> #include<cmath> #i

【BZOJ1294】[SCOI2009]圍豆豆動態規劃,狀壓

bool pre max += 網格 中心 是否 ret algo 【BZOJ1294】[SCOI2009]圍豆豆(動態規劃,狀壓) 題面 BZOJ 洛谷 題解 首先考慮如何判斷一個點是否在一個多邊形內(不一定是凸的),我們從這個點開始,朝著一個方向畫一條射線,看看它和這個

BZOJ4011 HNOI2015落憶楓音動態規劃+拓撲排序

  DAG中每個點選一條入邊就可以構成一棵有向樹,所以如果沒有環答案就是∏degreei。   考慮去掉含環的答案。可以看做把環縮點,剩下的點仍然可以任意選入邊。於是去除的方案數即為∏degreei/∏degreek,k為環上點。   環相當於考慮新加入邊的終點到起點的所有路徑。設f[i]為i為起點的所有

LeetCode打家劫舍問題動態規劃演算法的理解

前幾天在LeetCode的時候碰到幾道打家劫舍問題,覺得挺有意思,在這裡跟大家一起學習一下。首先我們來看第一題: 相信有的朋友拿到這道題想法是和我一樣的,那就是暴力解法,我用兩層for迴圈來遍歷每一個房間,後來發現這樣其實太麻煩了,並且還無法考慮到一些特定的情況,所以提交了很多次都

BZOJ4887 Tjoi2017可樂動態規劃+矩陣快速冪

  設f[i][j]為第i天到達j號城市的方案數,轉移顯然,答案即為每天在每個點的方案數之和。矩乘一發即可。 #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #

最長公共子串LCS問題動態規劃及備忘錄方法

動態規劃與備忘錄的LCS實現 動態規劃從下到上積累能量,中間值全部記錄以方便後期計算時呼叫,但有些時候很多記錄的值用不到,這個時候備忘錄方法則更好,可以減少記錄的值,其特點是自上到下,記錄少部分值。以LCS最長公共子串問題威力,分別給出兩種實現。 動態規劃法: pa

面試題14:剪繩子動態規劃,貪心演算法

一、題目: 一根長度為n的繩子,剪成m段,m,n都大於1,且都為整數,每段長度記為k[0],k[1]…,k[m].求k[0]*k[1]…*k[m]可能的最大乘積 1.1解法: 兩種不同的方法解決這個問題,先用常規的需要O(n²)時間和O(n)空間的動態規劃,接著用只需要O(1)的

BZOJ5298 CQOI2018交錯序列動態規劃+矩陣快速冪

  顯然答案為Σkb·(n-k)a·C(n-k+1,k)。並且可以發現ΣC(n-k,k)=fibn。但這實際上沒有任何卵用。   純組合看起來不太行得通,換個思路,考慮一個顯然的dp,即設f[i][j][0/1]為前i為選了j個1其中第i位是0/1的方案數。這樣當然能求出答案,複雜度O(n2)。   注意