[NOIP2005 普及組] 採藥
題目描述
辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫師。為此,他想拜附近最有威望的醫師為師。醫師為了判斷他的資質,給他出了一個難題。醫師把他帶到一個到處都是草藥的山洞裡對他說:“孩子,這個山洞裡有一些不同的草藥,採每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間裡,你可以採到一些草藥。如果你是一個聰明的孩子,你應該可以讓採到的草藥的總價值最大。”
如果你是辰辰,你能完成這個任務嗎?
輸入格式
第一行有 \(2\) 個整數 \(T\)(\(1 \le T \le 1000\))和 \(M\)(\(1 \le M \le 100\)),用一個空格隔開,\(T\) 代表總共能夠用來採藥的時間,\(M\) 代表山洞裡的草藥的數目。
接下來的 \(M\) 行每行包括兩個在 \(1\) 到 \(100\) 之間(包括 \(1\) 和 \(100\))的整數,分別表示採摘某株草藥的時間和這株草藥的價值。
輸出格式
輸出在規定的時間內可以採到的草藥的最大總價值。
樣例 #1
樣例輸入 #1
70 3
71 100
69 1
1 2
樣例輸出 #1
3
提示
【資料範圍】
- 對於 \(30\%\) 的資料,\(M \le 10\);
- 對於全部的資料,\(M \le 100\)。
【題目來源】
NOIP 2005 普及組第三題
題解
太弱了,先寫一個基本的01揹包練手
二維dp寫法,在下文巢狀迴圈中,第一層處 \(i\) 用來遍歷每株草藥,第二層用 \(j\) 遍歷每時長,
\(dp[i][j]\) 在每次迴圈中,記錄嘗試併入每一株草藥後的資訊:\(i\) 代表嘗試併入的草藥株數,\(j\) 代表所用時間(可能大於所有已經併入的草藥
需時間),\(dp[i][j]\)的值 代表此時總的價值
#include <iostream>
using namespace std;
int T, M; // 總共可用時間 T 和草藥數量 M
int dp[1001][1001]; // dp[i][j]前 i 種草藥在時間 j 內的最大價值
int t[1001], m[1001]; // 草藥時間,價值
int main()
{
//可用時間,草藥數量
cin >> T >> M;
for (int i = 1; i <= M; i++) {
//第i株草藥的時間和價值
cin >> t[i] >> m[i];
for (int j = 0; j <= T; j++) {
// 如果當前時間 j 小於所需時間 t[i],不能採摘該草藥
if (j < t[i]) {
dp[i][j] = dp[i - 1][j]; // 價值與不採摘該草藥時相同
}
//反之,可以採摘
else {
// 選擇採摘該草藥與不採摘該草藥的最大價值
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - t[i]] + m[i]);
}
}
}
// 輸出在時間 T 內可以獲得的最大總價值
cout << dp[M][T] << endl; // 取最後一行最後一列的值
return 0;
}
然後是一維dp寫法,
#include <iostream>
using namespace std;
int T, M; // 總共可用時間 T 和草藥數量 M
int dp[1001]; // dp[j]在時間 j 內的最大價值
int t[1001], m[1001]; // 草藥時間,價值
int main()
{
//可用時間,草藥數量
cin >> T >> M;
for (int i = 1; i <= M; i++) {
//第i種草藥的時間和價值
cin >> t[i] >> m[i];
for (int j = T; j >= t[i]; j--) {
// 選擇採摘該草藥與不採摘該草藥的最大價值
dp[j] = max(dp[j], dp[j - t[i]] + m[i]);
}
}
// 輸出在時間 T 內可以獲得的最大總價值
cout << dp[T] << endl; // 取最後一項的值
return 0;
}
搭配購買
題目描述
明天就是母親節了,電腦組的小朋友們在忙碌的課業之餘挖空心思想著該送什麼禮物來表達自己的心意呢?聽說在某個網站上有賣雲朵的,小朋友們決定一同前往去看看這種神奇的商品,這個店裡有 \(n\) 朵雲,雲朵已經被老闆編號為 \(1,2,3,...,n\),並且每朵雲都有一個價值,但是商店的老闆是個很奇怪的人,他會告訴你一些雲朵要搭配起來買才賣,也就是說買一朵雲則與這朵雲有搭配的雲都要買,電腦組的你覺得這禮物實在是太新奇了,但是你的錢是有限的,所以你肯定是想用現有的錢買到儘量多價值的雲。
輸入格式
第一行輸入三個整數,\(n,m,w\),表示有 \(n\) 朵雲,\(m\) 個搭配和你現有的錢的數目。
第二行至 \(n+1\) 行,每行有兩個整數, \(c_i,d_i\),表示第 \(i\) 朵雲的價錢和價值。
第 \(n+2\) 至 \(n+1+m\) 行 ,每行有兩個整數 \(u_i,v_i\)。表示買第 \(u_i\) 朵雲就必須買第 \(v_i\) 朵雲,同理,如果買第 \(v_i\) 朵就必須買第 \(u_i\) 朵。
輸出格式
一行,表示可以獲得的最大價值。
樣例 #1
樣例輸入 #1
5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2
樣例輸出 #1
1
提示
- 對於 \(30\%\) 的資料,滿足 \(1 \le n \le 100\);
- 對於 \(50\%\) 的資料,滿足 \(1 \le n, w \le 10^3\),\(1 \le m \le 100\);
- 對於 \(100\%\) 的資料,滿足 \(1 \le n, w \le 10^4\),\(0 \le m \le 5 \times 10^3\)。
題解
題目中“買第 \(u_i\) 朵雲就必須買第 \(v_i\) 朵雲,同理,如果買第 \(v_i\) 朵就必須買第 \(u_i\) 朵”將搭配的雲朵捆綁,可以看作是同一個雲朵
對此,可以用並查集將每一組搭配的雲朵合併,再將價格和價值加到根節點的雲朵上,並清零被合併雲朵的價格價值。
由於記憶體限制,只能用一維dp陣列
程式碼
#include<iostream>
using namespace std;
const int M = 1e4 + 5;
// n雲朵的數量,m搭配數量,w總預算
int n, m, w;
// c[i]:第i朵價格,d[i]:第i朵價值
int c[M], d[M], dp[M]; // dp陣列用於動態規劃儲存每個預算下的最大價值
int pre[M]; // 父節點
int root(int x);
int main()
{
//n數量,m搭配數量,w總預算
cin >> n >> m >> w;
// 讀取每朵價格,價值
for (int i = 1; i <= n; i++) {
cin >> c[i] >> d[i]; // c[i]為價格,d[i]為價值
pre[i] = i; // 初始化,設每個雲的父節點為自己
}
for (int k = 1; k <= m; k++) {
int a, b; // 讀取雲朵
cin >> a >> b;
int x = root(a), y = root(b); // 找根節點
// 如果不是同一集合
if (x ^ y) {
// 合併這兩個雲的價格和價值
c[y] += c[x]; d[y] += d[x];
c[x] = d[x] = 0; // 清空被合併的雲朵的價格和價值
pre[x] = y; // 令x的父節點指向y
}
}
// 動態規劃:計算在預算w的條件下,能獲得的最大價值
for (int i = 1; i <= n; i++) {
if (d[i]) // 如果第i朵雲朵的價值不為0
// 遍歷預算,當買得起時
for (int j = w; j >= c[i]; j--) {
// 更新dp陣列,選擇購買或不購買當前雲的情況
dp[j] = max(dp[j], dp[j - c[i]] + d[i]);
}
}
// 查詢在所有預算中的最大價值
int ans = 0;
for (int i = 1; i <= w; i++) {
ans = max(ans, dp[i]); // 更新答案為最大價值
}
// 輸出最大價值
cout << ans;
return 0;
}
// 查詢並查集根節點的函式,使用路徑壓縮最佳化
int root(int x)
{
if (pre[x] == x) return x; // 如果x是根節點,直接返回
return pre[x] = root(pre[x]); // 遞迴查詢,並路徑壓縮
}