方格取數(洛谷P7074)
https://www.luogu.com.cn/problem/P1004
這道題說是要求兩條從A到B的路徑,路徑上的數會被取走,(一個數字只能被取一次)使取走的數字和最大,並輸出這個最大值。
看完就能發現核心內容如下
(1)A到B的兩條路徑
這個很好解決,只要兩個二重迴圈分別列舉兩條路徑就行
(2)一個數被取走後變為0
換言之,一個數只能被兩條路徑中的一條經過,因此,我們就發現兩層迴圈分別列舉兩條路徑是不可行的,
因為這個新的條件顯然使兩條路徑上的各點座標產生了一定的特殊關係 or 在方程轉移的過程中取數的操作有一些特殊要求,以保證此條件成立。
既然我們發現這兩條路徑不是相互獨立的,那麼就能夠想到把用一個四重迴圈來同時列舉兩條路徑
初步dp式:dp[i][j][k][q] = max{dp[i][j][k][q],max(dp[i-1][j][k-1][q],dp[i-1][j][k][q-1],dp[i][j-1][k-1][q],dp[i][j-1][k][q-1])+sav[i][j] + sav[k][q]}
兩個點的轉移一共有四種情況(左左,左上,上左,上上)
這個dp式顯然還是沒有對兩條路徑進行限制,因此我們下一步要做的就是把這個限制補充上去
(1)首先會想到有可能是對兩個點的座標進行特判
即if(i != k && j != q)才進行轉移,否則不轉移
但是簡單想一下就會發現這麼做肯定不行,因為很有可能會出現最優情況中兩條路徑交叉
這個思路就被放棄了
但是其他更復雜的點的關係並不能被找到
(2)因此我們就選擇對轉移過程中取數這一操作進行調整
想像一下,如果兩條路徑在列舉過程中,同時列舉到了同一個點,因此這個點上的數值就被取了兩次,這肯定是不對的。
所以我們此時加一個特判:如果兩點重合,那麼這個點的數值只取一次
正確程式碼就有了
#include<cstdio> #include<algorithm> using namespace std; int main() { int n, sav[15][15] = {}, dp[15][15][15][15] = {}, a, b, c; scanf("%d", &n); do { scanf("%d%d%d", &a, &b, &c); sav[a][b] = c; }while(a != 0); for(int i = 1;i <= n;i++) { for(int j = 1;j <= n;j++) { for(int k = 1;k <= n;k++) { for(int q = 1;q <= n;q++) { if(i != k && j != q) { dp[i][j][k][q] = max(dp[i][j][k][q],max(dp[i-1][j][k-1][q],max(dp[i-1][j][k][q-1],max(dp[i][j-1][k-1][q],dp[i][j-1][k][q-1]))) + sav[i][j] + sav[k][q]); } else { dp[i][j][k][q] = max(dp[i][j][k][q],max(dp[i-1][j][k-1][q],max(dp[i-1][j][k][q-1],max(dp[i][j-1][k-1][q],dp[i][j-1][k][q-1]))) + sav[i][j]); } } } } } printf("%d", dp[n][n][n][n]); return 0; } /* 8 2 3 13 2 6 6 3 5 7 4 4 14 5 2 21 5 6 4 6 3 15 7 2 14 0 0 0*/
一點其他的廢話:
(1) 有人可能回想在操作過程中對取過的數打標記,來保證這個數不會被取多次。
如果要寫遞推格式的話這種操作肯定是不行的,記憶化搜尋到是可以這麼幹,因為記憶化搜尋可以回溯但是遞推顯然不能啊。
總結一下:需要回溯的操作就儘量不要在遞推裡嘗試了,因為極有可能(基本可以說是一定)不能實現
(2)我的做題歷程:
先寫了if(i != k && j != q)才轉移的談判
然後就發現輸出全是0(根本就不會進入最後的共同重點啊)
於是刪了特判,發現輸出的答案變大了,說明有一個點被取了多次(感謝這個善良的樣例體現出了這個問題)
然後就寫出了正解
感覺這道題還是挺典型的