1. 程式人生 > 遊戲攻略 >《盜賊遺產2》以諾的計劃互動點位說明

《盜賊遺產2》以諾的計劃互動點位說明

題目連結:https://www.acwing.com/problem/content/description/902/

題目描述

一個正整數 n 可以表示成若干個正整數之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。
我們將這樣的一種表示稱為正整數 n 的一種劃分。
現在給定一個正整數 n,請你求出 n 共有多少種不同的劃分方法。

輸入描述

共一行,包含一個整數 n。

輸出描述

共一行,包含一個整數,表示總劃分數量。
由於答案可能很大,輸出結果請對 109+7 取模。
1≤n≤1000

示例

輸入

5

輸出

7

借鑑完全揹包問題求解

切入點

通過分析整數劃分這個問題,我們可以發現它與完全揹包問題具有一定的相似性。待劃分的整數相當於揹包的容量,要劃分成的數字相當於要裝入揹包的物品。因此,我們就會想到化用完全揹包問題的狀態轉移方程。

狀態表示

f(i,j)表示滿足如下條件劃分方案的數量:

  • 劃分成的數字小於等於i
  • 劃分後的數字的總和等於j

注意於完全揹包的不同,此處的f(i,j)表示的是方案數不是最大值。

狀態劃分與計算

與完全揹包相同,對於f(i,j)我們劃分如下:

  • 用了0個i,方案數即f(i-1,j)
  • 用了1個i,方案數即f(i-1,j-i)
  • 用了2個i,方案數即f(i-1,j-2*i)
    ·········

因此,初步的狀態轉移方程為:
f(i,j)=f(i-1,j)+f(i-1,j-i)+··· (j-k*i>=0)
在完全揹包問題中,我們通過等價變形狀態轉移方程進行了優化,此處我們也進行嘗試:
f(i,j)=f(i-1,j)+f(i-1,j-i)+f(i-1,j-2*i)···


f(i,j-i)=f(i-1,j-i)+f(i-1,j-2*i)+···
因此,優化後的狀態轉移方程為:
f(i,j)=f(i-1,j)+f(i,j-i)
最後,與完全揹包一樣,可以將f(i,j)降為一維優化,不再贅述,AC程式碼為一維版本。

初始邊界處理

顯然:
f(i,0)=0
f(0,i)=0
考慮一下f(0,0),不劃分與之對應,f(0,0)=1,只要從f(i,j)的意義上入手,所有f(i,j)保持一致,應該就能確定正確的初始值。
如果感覺不保險,就用邊界值代進去試試,在這裡就用f(1,1),f(1,1)算出來對就行。

另一種思路

狀態表示

f(i,j)為滿足如下的條件的劃分方案數:

  • 劃分成的數字總和為i
  • 劃分成的數字的個數為j

狀態劃分與計算

對於f(i,j)所代表的狀態,我們進行如下劃分:

  • 方案中所劃分成的最小數字是1
  • 方案中所劃分的最小數字大於1

記劃分一的方案數為x,劃分二的方案數為y。
我們將劃分一中每個方案的那個1去掉,得到的新方案與原方案數量一致,將新方案集合與f(i-1,j-1)對照。首先,劃分一中的方案將變成和為i-1,共j-1項,符合f(i-1,j-1)的狀態要求,所以x<=f(i-1,j-1)。再將f(i-1,j-1)所代表的方案均加上一個1,將變成和為i,共j項,符合劃分一,即f(i-1,j-1)<=x。所以,x=f(i-1,j-1)。
同理,我們將劃分二中每一項減去1,將新方案集合與f(i-j,j)對照。可以得到y=f(i-j,j),證明方法同上,不再贅述。
綜上,狀態轉移方程為:
f(i,j)=f(i-1,j-1)+f(i-j,j)
另外,這個無法優化成一維,因為得到f(i,j)需要上面幾行的資料,必須儲存下來以供計算。

完全揹包思路的AC程式碼


#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1001;
const int mod = 1e9 + 7;

int n;
int f[N];

int main()
{
	cin >> n;
	f[0]=1;
	for (int i = 1; i < N; i++)
		for (int j = i; j <= n; j++)
			f[j] = (f[j] + f[j-i]) % mod;
	cout << f[n] << endl;
	return 0;
}

非完全揹包思路的AC程式碼


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1001;
const int mod = 1e9 + 7;

int n;
int f[N][N]; //邊界處理 自動初始化為0 沒寫程式碼但要知道應該初始化

int main()
{
	cin >> n;
	f[0][0] = 1; //邊界處理
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= i; j++)
			f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;

	int res = 0;
	for (int i = 1; i <= n; i++)
		res = (res + f[n][i]) % mod;
	cout << res << endl;
	return 0;
}