原題連結:https://www.luogu.com.cn/problem/P1541
題意解讀:m張卡片,每張卡片數字1-4,不同的卡片排列,導致不同的走法,也產生不同的總分數,求最大分數。
解題思路:
首先想到的是暴力列舉,透過dfs列舉不同的卡片排列,然後不同的排列計算分數,求最大值
有兩種方式實現排列:
1、列舉每一張卡片,根據每張卡片是否已經使用過進行遞迴排列
2、列舉卡片的數字1-4,根據當前數字的卡片是否有剩餘來進行遞迴排列
由於相同數字的卡片有多張,第一種方法的排列勢必導致重複,比如第一張、第二張卡片都是1,用第一種方法列舉會出現“第一張 第二張”“第二張 第一張”兩種情況,而實際上對應的卡片數字都是1 1,因此採用第二種方式進行dfs,儘量減少重複。
30分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N= 400;
int n, m;
int a[N], b[5]; //a記錄所有格子的分數,b[5]記錄1~4卡片的張數
int ans;
//第k張卡片,第x格,當前總分是sum
void dfs(int k, int x,int sum)
{
if(k == m + 1 && x == n)
{
ans = max(ans, sum);
return;
}
for(int i = 1; i <= 4; i++) //遍歷卡片數字1~4可以減少全排列的重複,如果是直接遍歷每張卡片會導致較多的重複,更耗時
{
if(b[i] > 0)
{
b[i]--;
dfs(k + 1, x + i, sum + a[x + i]);
b[i]++;
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
int x;
for(int i = 1; i <= m; i++)
{
cin >> x;
b[x]++;
}
dfs(1, 1, a[1]);
cout << ans;
return 0;
}
DFS有沒有最佳化的可能呢?是有的,可以藉助於記憶化+剪枝
設f[i][j]記錄用i個卡走到j格時的得分,這樣每次dfs之前,判斷如果本次i、j對應的f[i][j]已經存在且>=當前的得分,則可以停止dfs,因為在消耗了相同卡片數量且到達相同的格子,如果得分比之前的情況還少,後面肯定大機率是無法超越了。
50分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N= 400, M = 200;
int n, m;
int a[N], b[5]; //a記錄所有格子的分數,b[5]記錄1~4卡片的張數
int ans;
int f[M][N]; //f[i][j]記錄用i個卡走到j的得分
//第k張卡片,第x格,當前總分是sum
void dfs(int k, int x,int sum)
{
if(f[k][x] >= sum) return; //如果得分比之前的小,不用繼續
f[k][x] = sum;
if(k == m + 1 && x == n)
{
ans = max(ans, sum);
return;
}
for(int i = 1; i <= 4; i++) //遍歷卡片數字1~4可以減少全排列的重複,如果是直接遍歷每張卡片會導致較多的重複,更耗時
{
if(b[i] > 0)
{
b[i]--;
dfs(k + 1, x + i, sum + a[x + i]);
b[i]++;
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
int x;
for(int i = 1; i <= m; i++)
{
cin >> x;
b[x]++;
}
dfs(1, 1, a[1]);
cout << ans;
return 0;
}
本題的正解應該是動態規劃,但是狀態如何表示呢?
可以考慮最後一步的情況,最後一步,可能走1/2/3/4格,這樣,可以直接把1、2、3、4對應的卡片張數作為狀態!
狀態表示:dp[i][j][k][l]表示使用i張1卡片,j張2卡片,k張3卡片,l張4卡片能得到的最大分數
狀態計算:最後一步可能走1或2或3或4格,則有dp[i][j][k][l] = max(dp[i-1][j][k][l] + a[i*1+j*2+k*3+l*4+1],dp[i][j-1][k][l] + a[i*1+j*2+k*3+l*4+1], dp[i][j][k-1][l] + a[i*1+j*2+k*3+l*4+1]), dp[i][j][k][l-1] + a[i*1+j*2+k*3+l*4+1],i*1+j*2+k*3+l*4+1表示用完i、j、k、l張卡片後走到的位置,注意要判斷i-1,j-1,k-1,l-1非負
初始值:dp[0][0][0][0][0] + a[1] , 一張卡片都不用,初始得分就是第一個格子的分數
結果:dp[b[1]][[b[2]][b[3]][b[4]] ,也就是消耗所有的1,2,3,4卡片能得到的最大分數
100分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N= 400, M = 45;
int n, m;
int a[N], b[5]; //a記錄所有格子的分數,b[5]記錄1~4卡片的張數
int dp[M][M][M][M]; //dp[i][j][k][l]表示使用i張1卡片,j張2卡片,k張3卡片,l張4卡片能得到的最大分數
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
int x;
for(int i = 1; i <= m; i++)
{
cin >> x;
b[x]++;
}
dp[0][0][0][0] = a[1];
for(int i = 0; i <= b[1]; i++)
{
for(int j = 0; j <= b[2]; j++)
{
for(int k = 0; k <= b[3]; k++)
{
for(int l = 0; l <= b[4]; l++)
{
int idx = i * 1 + j * 2 + k * 3 + l * 4 + 1; //用完i,j,k,l張卡片後走到的位置
if(i > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i-1][j][k][l] + a[idx]);
if(j > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j-1][k][l] + a[idx]);
if(k > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k-1][l] + a[idx]);
if(l > 0) dp[i][j][k][l] = max(dp[i][j][k][l], dp[i][j][k][l-1] + a[idx]);
}
}
}
}
cout << dp[b[1]][b[2]][b[3]][b[4]];
return 0;
}