1. 程式人生 > >浙大PAT CCCC L3-001 湊零錢 ( 0/1背包 && 路徑記錄 )

浙大PAT CCCC L3-001 湊零錢 ( 0/1背包 && 路徑記錄 )

esp AR pre 倒序輸出 情況 sof font ron src

題目鏈接

分析 :

就是一個 0/1 背包,但是需要記錄具體狀態的轉移情況

這個可以想象成一個狀態轉移圖,然後實際就是記錄路徑

將狀態看成點然後轉移看成邊,最後輸出字典序最小的路徑

這裏有一個很巧妙的做法

先將所有的硬幣升序排序(這一點很重要)

然後在這一條件下,假設當前狀態是考慮第 i 個硬幣,前一個狀態是考慮第 i-1 個硬幣

試想對於同一個體積,如果選用的硬幣數量越多是不是字典序更小

然後對於如果對於同一體積下,選用硬幣數一樣多的兩種方案

由於我們已經升序排序,如果有一樣多硬幣的情況,那麽永遠選後面面值大的更新答案

因為體積固定嘛,那麽後面選用的硬幣面值更大,湊成一樣的體積,是不是說明

其他的選用硬幣應該更小,也就是字典序更小了?

技術分享圖片
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
const int INF = 0x3f3f3f3f;
struct Status{ int Coin, Pre; }st[maxn];///記錄狀態
int dp[maxn], Coin[maxn];
int N, M;

int main(void)
{
    while(~scanf("%d %d", &N, &M)){
        for(int i=0; i<N; i++)
            scanf(
"%d", &Coin[i]); sort(Coin, Coin+N); for(int i=0; i<=M; i++) st[i].Coin = st[i].Pre = -1, dp[i] = -INF;///初始化為負無窮小,對於背包是否剛剛好裝滿 ///一般都選擇這樣初始化 dp[0] = 0; for(int i=0; i<N; i++){ for(int j=M; j>=Coin[i]; j--){
if(dp[j - Coin[i]] + 1 >= dp[j]){///對於 == 的情況,可以去用題目給的第一個樣例, ///然後去掉這個等號輸出答案,再想一下為什麽錯了,可能理解就更多了 dp[j] = dp[j - Coin[i]] + 1; st[j].Coin = Coin[i];///記錄當前狀態是選了哪個硬幣 st[j].Pre = j - Coin[i];///記錄從哪個狀態轉移過來 } } } if(dp[M] > 0){ vector<int> ans; int now = M; while(st[now].Coin != -1){///倒序回復路徑 ans.push_back(st[now].Coin); now = st[now].Pre; } for(int i=ans.size()-1; i>=0; i--){///倒序輸出倒序路徑就是正確路徑了 printf("%d", ans[i]); if(i != 0) putchar( ); }puts(""); }else puts("No Solution"); } return 0; }
View Code

浙大PAT CCCC L3-001 湊零錢 ( 0/1背包 && 路徑記錄 )