1. 程式人生 > >【習題詳解】動態規劃DP:硬幣遊戲 蛋糕

【習題詳解】動態規劃DP:硬幣遊戲 蛋糕

動態規劃DP

硬幣

題目描述 農夫約翰的奶牛喜歡玩硬幣遊戲,因此他發明了一種稱為“Xoinc”的兩人硬幣遊戲。 初始時,一個有N(5 <= N <= 2,000)枚硬幣的堆疊放在地上,從堆頂數起的第I枚硬幣的幣值為C_i (1 <= C_i <= 100,000)。 開始玩遊戲時,第一個玩家可以從堆頂拿走一枚或兩枚硬幣。如果第一個玩家只拿走堆頂的一枚硬幣,那麼第二個玩家可以拿走隨後的一枚或兩枚硬幣。如果第一個玩家拿走兩枚硬幣,則第二個玩家可以拿走1,2,3,或4枚硬幣。在每一輪中,當前的玩家至少拿走一枚硬幣,至多拿走對手上一次所拿硬幣數量的兩倍。當沒有硬幣可拿時,遊戲結束。 兩個玩家都希望拿到最多錢數的硬幣。請問,當遊戲結束時,第一個玩家最多能拿多少錢呢? 輸入格式

第1行:1個整數N 第2…N+1行:第i+1行包含1個整數C_i 輸出格式 1個整數表示第1個玩家能拿走的最大錢數。 input 5 1 3 1 7 2 output 9

好難啊…看了我0.5h的題解才看懂的… 這道題目我們可以選擇倒著推:因為最後一個因此必然是要取完的,因此可以確定狀態一定是可行的。 我們給每一個金幣自底向上標號為1-i(這裡自然需要逆序讀入) 狀態:設f[i][j]表示某一方取完以後還剩下編號為1-i的金幣,上一個人取了j枚金幣的最大取值(顯然上一個人取的金幣編號一定大於i)。 轉移:對於每一個狀態我們必然需要知道一個當前所取的金幣數k,必然和f[i-k][]有關,表示1-i堆中取了k,還剩下1~i-k枚金幣;同樣,也和f[][k]有關,前一個人轉移到這個人的時候,當前取的是k,那麼必然就是k;那麼這個人當前取的金幣數就是總金幣減去下一個決策的金幣(這裡用sum[]記錄金幣的字首和),可得狀態轉移方程:f

[i][j]=max(sum[i]f[ik]),ikf[i][j]=max(sum[i]-f[i-k]),i≥k 優化:觀察每一個f[i][j],對於每一個f[i][j-1],多餘的決策只有選2j-1枚金幣和2j枚,只要k等於這兩個值做兩遍轉移,和原來求最大值取一個max即可。

#include<bits/stdc++.h>
using namespace std;
#define maxn 3000

int a[maxn];
int sum[maxn]; 
int f[maxn][maxn];
//表示其中的一個人,拿金幣的時候剩下了1-i,上一個人拿了j個金幣的最大值 
inline void read(int &readnum) { int s=0,w=1;char c=getchar(); while (c<'0' || c>'9') {if (c=='-') w=-1;c=getchar();} while (c>='0' && c<='9') s=s*10+c-48,c=getchar(); readnum=s*w; } int main(void) { freopen("xoinc..in","r",stdin); freopen("xoinc..out","w",stdout); int n; read(n); for (int i=n;i;--i) read(a[i]); for (int i=1;i<=n;++i) sum[i]=sum[i-1]+a[i]; for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) { f[i][j]=f[i][j-1]; int k=2*j-1; if (i>=k) f[i][j]=max(f[i][j],sum[i]-f[i-k][k]); k=2*j; if (i>=k) f[i][j]=max(f[i][j],sum[i]-f[i-k][k]); } cout<<f[n][1]; return 0; }

蛋糕塔

題目描述 Hl高中要舉行一場蛋糕塔比賽。注意,不是蛋糕比賽,而是蛋糕塔比賽。 學校會提供N種不同型別的蛋糕,第i種蛋糕的高度為Hi(5 <= H_i <= T),營養價值為Vi(1 <= Vi<= 1,000,000),並且保證所有蛋糕的高度為5的整數倍,每種型別的蛋糕沒有數量限制。 蛋糕塔比賽的規則就是要求按照提供的蛋糕,壘成一個高度不超過T(1 <= T <= 1,000)的蛋糕塔,並且要求這個蛋糕塔所有蛋糕的營養價值累加和最高。 因為蛋糕不是很結實,參加比賽的小x發現一個現象,如果某塊蛋糕的高度超過K,那麼這塊蛋糕下面的所有蛋糕的高度都將被壓縮為自己高度的4/5,但是營養價值不會丟失。發現這個情況後的小x很興奮,現在他想知道,如何安排自己的蛋糕塔,能讓營養價值最高。 輸入格式 第一行三個整數:N,T和K; 接下來N行,每行兩個整數:Vi 和 Hi。 輸出格式 一行,一個整數,表示答案。 樣例資料 input 3 53 25 100 25 20 5 40 10 output 240

這道題完全屬於一個完全揹包的變形。對於不會完全揹包可以戳這裡,這裡不再多講。 對於這道題,一共有兩種情況: 1.不用大蛋糕–>用小蛋糕做一次完全揹包即可。 2.用大蛋糕–>把所有蛋糕壓成五分之四做一遍完全揹包得到陣列f[i](其含義代表體積為i的揹包所能容下的最大價值),對於每一個大蛋糕,答案就是f[t-h[i]]. 程式碼如下:

#include<bits/stdc++.h>
using namespace std;
#define maxn 10000
#define maxT 120000 

int n,t,k;
int h[maxn];
int v[maxn];
int f[maxT];

inline void read(int &readnum)
{
	int s=0,w=1;char c=getchar();
	while (c<'0' || c>'9') {if (c=='-') w=-1;c=getchar();}
	while (c>='0' && c<='9') s=s*10+c-48,c=getchar();
	readnum=s*w;
}

int main(void)
{
	freopen("cheese..in","r",stdin);
	freopen("cheese..out","w",stdout);
	read(n);
	read(t);
	read(k);
	for (int i=1;i<=n;++i) read(v[i]),read(h[i]);
	for (int i=1;i<=n;++i)
	    for (int j=h[i];j<=t*5/4;++j)
	        f[j]=max(f[j],f[j-h[i]]+v[i]);
	int ans=f[t];
	for (int i=1;i<=n;++i)
	    if (h[i]>=k) ans=max(ans,f[(t-h[i])*5/4]+v[i]);
	cout<<ans<<endl;
	return 0;
}