(3)M排N列的木樁,從第一排開始跳到最後一排。每次跳到下一排的同一列、前一列或後一列木樁。
設R[i,j]爲跳到i排j列木樁的獎金。求從第一排跳到最後一排獲得的獎金總量的最大值,給出遞推方程。
(4)上述問題,如果允許在同一排向左或向右跳,且橫跳總次數不超過H,給出求獎金總量的最大值的遞推方程
(5)如果每個木樁只有第一次跳上去的時候有獎金,如何求最大值
第4題相對簡單,所以放在文章的最後面。
下面是第五題的答案:
所要注意的是,在狀態轉移方程哪裏,我進行了很多代碼簡化,要不然代碼有點長。
思路:
首先這道題要考慮你是不是重複走到同一個點。
首先自然想到對自己走過的點打標記,但是這樣一張表是不夠的,而且不好記錄,以爲你是一行一行跑的,所以你對某一個狀態的路徑打了標記,會影響到其他狀態(其他狀態可能沒有經過這些點),狀態之間會有衝突,不能用一張表記錄。
solution 1:
首先,自然想到對每一個狀態獨一的用一張表記錄,但是如果一行一行跑的(所有狀態並行的跑的)話,要用分廠多的表記錄。
所以我們先把一個狀態跑完,然後在跑其他狀態,一個一個串行,這樣空間上只需要一張表了。
我們採用自頂向下帶備忘錄的形式泄動態規劃。
但是記錄狀態結果是要注意,要想數位DP一樣,無約束時才記錄(數位DP時前面的state == ture),這裏只有第一次到達該行的結果採用數組記錄下來。
這個方法還是時間複雜度有點高,後面有更好的方法,不過需要一點創造力。
下面看這種解法的代碼。
#include<bits/stdc++.h>
#define per(i,a,b) for(int i = (a);i <= (b); ++i)
using namespace std;
const int maxn=1e3;
int n = 0,m = 0,h = 0;
int r[maxn+10][maxn+10];
int dp[100][100][100];
int vis[maxn+10][maxn+10];
int dfs(int x,int y,int hn,bool sta){//到達x,y,橫着走了hn次,sta是否是第一次到達該行
if(x == m && hn == h){//遞歸終止條件
return (vis[x][y] == 0) ? r[x][y] : 0;
}
if(sta &&dp[x][y][hn] != 0){//sta表示第一次到達這一行,那麼可以返回原來計算的結果,這有點類似數位DP
return dp[x][y][hn];
}
int bonus = (vis[x][y] == 0 ) ? r[x][y] : 0;//只有第一次碰到,纔有獎金,所以vis是一個整數數組,bool數組不夠用
++vis[x][y];
int res = 0;
if(hn + 1 <= h){
if(y-1 >= 1){
res = dfs(x,y-1,hn+1,false);
}
if(y+1 <= n){
res = max(res,dfs(x,y+1,hn+1,false));
}
}
if(x != m){
if(y - 1 >= 1){
res = max(res,dfs(x+1,y-1,hn,true));
}
res = max(res,dfs(x+1,y,hn,true));
if(y+1 <= n){
res = max(res,dfs(x+1,y+1,hn,true));
}
}
if(sta){//類似於數位DP,如果是第一次到達該行的值,那麼就記錄下來,其他的會有衝突
dp[x][y][hn] = max(res + bonus,dp[x][y][hn]);
}
--vis[x][y];
return res + bonus;
}
int main(){
//std::ios::sync_with_stdio(false);
//cin.tie(0);cout.tie(0);
while(~scanf("%d %d %d",&m,&n,&h)){
per(i,1,m){
per(j,1,n){
scanf("%d",&r[i][j]);
}
}
memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis));
int maxv = -1,loc = 0;
per(i,1,n){
int ans = dfs(1,i,0,true);
if(ans > maxv){
loc = i;
maxv = ans;
}
}
printf("The starting position is %d, The maximum bonus you can get is %d\n",loc,maxv);
}
return 0;
}
下面是一種更好的解法:構造法。
首先畫出走重複路的情況:
-> ->
<- <- <- <- <- <-|
-> ->
。
這種情況下只有中間那一行的箭頭是有效的,兩邊的重複的可以看做無效的。
首先我們的難題是他在一行的橫跳的方向是不確定的,我們能否把他編程方向固定的呢?
帶着這樣的思考,中間的那一行只能有一種方向,向左或向右,我們兩種情況都枚舉。
那麼兩邊重複的怎麼辦?
既然他們是重複的,那麼可不可以看做獎金是0的點,所以兩邊可以構造成全是0的點。
所以在原來的圖上面,兩個行之間新構造0 0 0 0 。。。行,用來過渡。
所以問題就轉化爲原來的郵箱航只能有一種方向橫跳,就不用考慮重複問題了。
這裏的奇數行(有效行)和偶數行(0行)要分類討論。
狀態轉移方程:
dp[x][y][h][dic]表示第x行第y列橫跳了不超過h次,這行(第x行)的方向是dic(0:往左跳,1:往右跳)的最大獎金。
r[i][j]表示相應的獎金。
奇數行:
dp[x][y][h][0] = max(dp[x-1][y][h][0],dp[x-1][y][h][1],dp[x][y+1][h-1][0]) + r[i][j];
dp[x][y][h][1] = max(dp[x-1][y][h][0],dp[x-1][y][h][1],dp[x][y-1][h+1][1]) + r[i][j];
偶數行:
dp[x][y[[h][0] = max(dp[x-1][y-1][h][0和1],dp[x-1][y][h][0和1],dp[x-1][y+1][h][0和1] dp[x][y+1][h-1][0]) + r[i][j];(共7個)
dp[x][y[[h][1] = max(dp[x-1][y-1][h][0和1],dp[x-1][y][h][0和1],dp[x-1][y+1][h][0和1] dp[x][y-1][h-1][1]) + r[i][j];(共7個)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<map>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define pii pair<int,int>
#define all(x) x.begin(),x.end()
#define mem(a,b) memset(a,b,sizeof(a))
#define per(i,a,b) for(int i = a;i <= b;++i)
#define rep(i,a,b) for(int i = a;i >= b;--i)
const int maxn = 30;
int n = 0,m = 0,h = 0;
int dp[maxn*2][maxn][maxn][2];
int r[maxn*2][maxn];
void max_bonus(){
map<int,int> mp;
mp[0] = 1; mp[1] = -1;
per(i,1,2*m-1){
per(k,0,h){//h循環必須放在j外面,因爲由i&1==1的狀態轉移方程可知,
//dp[x][y][h][0] = max(...,dp[x][y+1][h-1][0]),
//y要調用y+1的結果,所以這個時候發現h調用的是h-1,所以h>y,h-1的時候計算出所有的y
per(dic,0,1){//這層循環可以放在這裏,也可以放在j裏面
per(j,1,n){
if(i & 1){
dp[i][j][k][dic] = max(dp[i-1][j][k][0],max(dp[i-1][j][k][1],(k > 0 ? dp[i][j+mp[dic]][k-1][dic] : 0))) + r[i][j];
//dp[i][j][k][0] = max(dp[i-1][j][k][0],max(dp[i-1][j][k][1],(k > 0 ? dp[i][j+1][k-1][0] : 0)) + r[i][j];
//dp[i][j][k][1] = max(dp[i-1][j][k][0],max(dp[i-1][j][k][1],(k > 0 ? dp[i][j+1][k-1][1] : 0)) + r[i][j];
}else{
per(p,0,1){
dp[i][j][k][dic] = max(dp[i][j][k][dic],max(dp[i-1][j-1][k][p],max(dp[i-1][j][k][p],dp[i-1][j+1][k][p])) + r[i][j]) ;
}
dp[i][j][k][dic] = max(dp[i][j][k][dic],(k > 0 ? dp[i][j+mp[dic]][k-1][dic]: 0) + r[i][j]) ;
}
}
}
}
}
per(i,1,m){
per(j,1,n){
int maxl = 0,maxr = 0;
per(k,0,h){
maxl = max(maxl,dp[i][j][k][0]);
maxr = max(maxr,dp[i][j][k][1]);
}
printf("%d(%d) ",maxl,maxr);
}
printf("\n");
}
int maxv = -1,loc = 0;
per(j,1,n){
//per(k,0,h){//最大值一定是在k == h處取得
per(dic,0,1){
if(dp[2*m-1][j][h][dic] >= maxv){
maxv = dp[2*m-1][j][h][dic];
loc = j;
}
}
//}
}
printf("%d %d\n",loc,maxv);
}
int main(){
while(~scanf("%d %d %d",&m,&n,&h)){
memset(dp,0,sizeof(dp)); memset(r,0,sizeof(r));
per(i,1,m){
per(j,1,n){
scanf("%d",&r[i*2-1][j]);
}
}
max_bonus();
}
return 0;
}
/*
test:
3 3 3
1 2 3
4 5 6
9 8 7
answer:3 38
1 3 4
1 2 3
6
*/
第4題代碼:
#include<bits/stdc++.h>
using namespace std;
#define per(i,a,b) for(int i=(a);i<=(b);i++)
int n = 0,m = 0,h = 0;
int r[10][10];
int dp[10][10][10];
void solve(){
memset(dp,0,sizeof(dp));
per(i,1,m){
per(k,0,h){//h循環要放在列循環前面,因爲列循環每一次都要用到第k-1的結果
per(j,1,n){
dp[i][j][k] = max(dp[i-1][j-1][k],max(dp[i-1][j][k],dp[i-1][j+1][k])) + r[i][j];
if(k == 0){
continue;
}
dp[i][j][k] = max(dp[i][j][k],max(dp[i][j-1][k-1],dp[i][j+1][k-1]) + r[i][j]);
}
}
}
int maxv = -1,loc = 0,times = 0;
per(i,1,n){
if(dp[m][i][h] > maxv){
maxv = dp[m][i][h];
loc = i;
}
}
printf("The starting position is %d, The maximum bonus you can get is %d\n",loc,maxv);
}
int main(){
//std::ios::sync_with_stdio(false);
//cin.tie(0);cout.tie(0);
while(~scanf("%d %d %d",&m,&n,&h)){
per(i,1,m){
per(j,1,n){
scanf("%d",&r[i][j]);
}
}
solve();
}
return 0;
}