P1002 過河卒
題目描述
棋盤上A點有一個過河卒,需要走到目標B點。卒行走的規則:可以向下、或者向右。同時在棋盤上C點有一個對方的馬,該馬所在的點和所有跳躍一步可達的點稱爲對方馬的控制點。因此稱之爲“馬攔過河卒”。
棋盤用座標表示,A點(0,0)、B點(n,m),同樣馬的位置座標是需要給出的。
現在要求你計算出卒從A點能夠到達B點的路徑的條數,假設馬的位置是固定不動的,並不是卒走一步馬走一步。
- 輸入:一行四個正整數,分別表示B點座標和馬的座標。
- 輸出:一個整數,表示所有的路徑條數。
解題步驟
- 初始化棋盤邊界爲1
- 狀態方程:f[i][j] = f[i-1][j] + f[i][j-1]
注意事項
- 初始化數組時應 +1
- 狀態方程的值會超過 int,可定義爲 unsigned long long
- 考慮“馬”處於橫向邊界時,該位置的右側均初始化爲0;處於縱向邊界時,該位置的下方均初始化爲0。
- 考慮“馬”可一步跳到的位置處於橫向縱向邊界的情況,即“馬”距離邊界1或2的情況。
- 更新狀態方程時,考慮經過位置是“馬”一步跳到的位置時,該位置上的值應爲0(不更新)。
提交代碼
#include<iostream>
#include<math.h>
using namespace std;
int main() {
int bx, by, hx, hy; // b->B點,h->“horse”馬的位置
cin >> bx >> by >> hx >> hy;
unsigned long long **map = new unsigned long long *[bx + 1];
for (int i = 0; i <= bx; i++) //動態二維數組
map[i] = new unsigned long long[by + 1];
for (int i = 0; i <= bx; i++)
map[i][0] = 1; //x軸邊界初始化
for (int j = 0; j <= by; j++)
map[0][j] = 1; //y軸邊界初始化
if (hx == 0) { //馬在x邊界
for (int j = hy; j <= by; j++)
map[0][j] = 0;
}
else if (hx == 1) { //馬可跳到的位置在y邊界(注:我沒寫錯,就是y邊界)
if (hy-2 >= 0) {
for (int j = hy-2; j <= by; j++)
map[0][j] = 0;
}
else if (hy + 2 <= by) {
for (int j = hy + 2; j <= by; j++)
map[0][j] = 0;
}
}
else if (hx == 2) { //馬可跳到的位置在y邊界
if (hy - 1 >= 0) {
for (int j = hy - 1; j <= by; j++)
map[0][j] = 0;
}
else if (hy + 1 <= by) {
for (int j = hy + 1; j <= by; j++)
map[0][j] = 0;
}
}
if (hy == 0) { //馬在y邊界
for (int i = hx; i <= bx; i++)
map[i][0] = 0
}
else if (hy == 1) { //馬可跳到的位置在x邊界
if (hx - 2 >= 0) {
for (int i = hx - 2; i <= bx; i++)
map[i][0] = 0;
}
else if (hx + 2 <= bx) {
for (int i = hx + 2; i <= bx; i++)
map[i][0] = 0;
}
}
else if (hy == 2) { //馬可跳到的位置在x邊界
if (hx - 1 >= 0) {
for (int i = hx - 1; i <= bx; i++)
map[i][0] = 0;
}
else if (hx + 1 <= bx) {
for (int i = hx + 1; i <= bx; i++)
map[i][0] = 0;
}
}
for (int i = 1; i <= bx; i++) {
for (int j = 1; j <= by; j++) {
if ((abs(i - hx) == 1 && abs(j - hy) == 2) || (abs(i - hx) == 2 && abs(j - hy) == 1) || (i == hx && j == hy))
map[i][j] = 0;
else
map[i][j] = map[i - 1][j] + map[i][j - 1];
}
}
cout << map[bx][by];
return 0;
}
P1044 棧
題目描述
寧寧考慮的是這樣一個問題:一個操作數序列“1,2,…,n”,棧A的深度大於n。現在可以進行兩種操作:
1. 將一個數,從操作數序列的頭端移到棧的頭端(對應數據結構棧的push操作)
2. 將一個數,從棧的頭端移到輸出序列的尾端(對應數據結構棧的pop操作)
使用這兩種操作,由一個操作數序列就可以得到一系列的輸出序列,你的程序將對給定的n,計算並輸出由操作數序列1,2,…,n經過操作可能得到的輸出序列的總數。
- 輸入:一個整數n(1≤n≤18)
- 輸出:可能輸出序列的總數目
解題步驟
- 如圖所示,規定從A到B只能夠向右或向上移動,右移爲入棧操作,上移爲出棧操作,所求A點到B點的路徑數目就是N個節點出棧序列的數目,並且從A點到B點的每一條路都代表一種出棧序列。
- 入棧次數>=出棧次數,向右次數>=向上次數。故只需右下部分矩陣(含對角線上的點)。
- 轉化爲棋盤的動態規劃問題,模仿“P1002過河卒”解決,注意該棋盤只有右下部分。
提交代碼
#include<iostream>
using namespace std;
int main() {
int n;
cin >> n;
unsigned long long **map = new unsigned long long *[n + 1];
for (int i = 0; i <= n; i++)
map[i] = new unsigned long long[n + 1];
for (int j = 0; j <= n; j++) //初始化下邊界
map[0][j] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (j > i)
map[i][j] = map[i - 1][j] + map[i][j - 1];
else if (j == i)
map[i][j] = map[i - 1][j];
else
continue;
}
}
cout << map[n][n];
return 0;
}
參考博客
- N個元素的進出棧總數-方法轉換-動態規劃
- 上述博客中還有優化方法——卡特蘭數,但本博客着重動態規劃,故不贅述。
P1048 採藥
題目描述
辰辰是個天資聰穎的孩子,他的夢想是成爲世界上最偉大的醫師。爲此,他想拜附近最有威望的醫師爲師。醫師爲了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裏對他說:“孩子,這個山洞裏有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裏,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”
- 輸入:第一行有2個整數T(1≤T≤1000)和M(1≤M≤100),用一個空格隔開,T代表總共能夠用來採藥的時間,M代表山洞裏的草藥的數目。接下來的M行每行包括兩個在1到100之間(包括1和100)的整數,分別表示採摘某株草藥的時間和這株草藥的價值。
- 輸出:1個整數,表示在規定的時間內可以採到的草藥的最大總價值。
解題步驟
- 典型的0/1揹包問題
- 遍歷考慮前i個物品的情況下的最優解
- 如果裝不下當前物品,那麼前i個物品的最佳組合和前i-1個物品的最佳組合是一樣的。
- 如果裝得下當前物品,有以下兩種假設,選取其中較大價值的一個,爲當前最佳組合的價值。
- 假設1:裝當前物品,在給當前物品預留了相應空間的情況下,前i-1個物品的最佳組合加上當前物品的價值就是總價值。
- 假設2:不裝當前物品,那麼前i個物品的最佳組合和前i-1個物品的最佳組合是一樣的。
- 註釋部分包含了回溯輸出索取物品是哪些
- 從後向前遍歷n個物品
- 剩餘空間相同的情況下,若前i個物品的組合與前i-1個物品的組合相同,則說明未裝當前物品。
- 若當前組合符合上述的假設1,則說明裝了當前物品,輸出即可。
- 注意,找出一個物品後,應將剩餘空間減少當前物品的重量。
提交代碼
#include<iostream>
using namespace std;
int main() {
int t, m;
cin >> t >> m;
int * cost = new int[m + 1];
int * value = new int[m + 1];
for (int i = 1; i <= m; i++)
cin >> cost[i] >> value[i];
cost[0] = 0;
value[0] = 0;
int ** dp = new int*[m + 1];
for (int i = 0; i <= m; i++) {
dp[i] = new int[t + 1];
for (int j = 0; j <= t; j++)
dp[i][j] = 0;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= t; j++) {
if (j >= cost[i])
dp[i][j] = dp[i - 1][j] > dp[i - 1][j - cost[i]] + value[i] ? dp[i - 1][j] : dp[i - 1][j - cost[i]] + value[i];
else
dp[i][j] = dp[i - 1][j];
}
}
cout << dp[m][t];
// 回溯揹包中的物品是哪些
/*
cout << endl << "What are they?" << endl;
int i = m, j = t;
while (i > 0) {
if (dp[i][j] == dp[i - 1][j]) {
i--;
continue;
}
else if (dp[i][j] == dp[i - 1][j - cost[i]] + value[i]) {
cout << cost[i] << " " << value[i] << endl;
j = j - cost[i];
}
i--;
}
*/
return 0;
}
參考博客
P1049 裝箱問題
題目描述
有一個箱子容量爲V(正整數,0≤V≤20000),同時有n個物品(0<n≤30,每個物品有一個體積(正整數)。要求n個物品中,任取若干個裝入箱內,使箱子的剩餘空間爲最小。
- 輸入:1個整數,表示箱子容量1個整數,表示有n個物品,接下來n行,分別表示這n個物品的各自體積。
- 輸出:1個整數,表示箱子剩餘空間。
解題步驟
- 典型0/1揹包問題
- 將物品體積同時看作空間消耗和價值即可
- 代碼同上題,僅需修改讀入參數即可,不再贅述。
P1060 開心的金明
題目描述
金明今天很開心,家裏購置的新房就要領鑰匙了,新房裏有一間他自己專用的很寬敞的房間。更讓他高興的是,媽媽昨天對他說:“你的房間需要購買哪些物品,怎麼佈置,你說了算,只要不超過N元錢就行”。今天一早金明就開始做預算,但是他想買的東西太多了,肯定會超過媽媽限定的N元。於是,他把每件物品規定了一個重要度,分爲5等:用整數1−5表示,第5等最重要。他還從因特網上查到了每件物品的價格(都是整數元)。他希望在不超過N元(可以等於N元)的前提下,使每件物品的價格與重要度的乘積的總和最大。
- 輸入:第一行,爲2個正整數,用一個空格隔開:nm(其中N(<30000)表示總錢數,m(<25)爲希望購買物品的個數。)從第2行到第m+1行,第j行給出了編號爲j−1的物品的基本數據,每行有2個非負整數vp(其中v表示該物品的價格(v≤10000),p表示該物品的重要度(1−5)
- 輸出:1個正整數,爲不超過總錢數的物品的價格與重要度乘積的總和的最大值(<100000000)。
解題步驟
- 典型0/1揹包問題
- 代碼僅需修改
dp[i][j] = dp[i - 1][j] > dp[i - 1][j - v[i]] + v[i] * w[i] ? dp[i][j] = dp[i - 1][j] : dp[i - 1][j - v[i]] + v[i] * w[i];
即可,不再贅述。