洛谷3146 [USACO16OPEN]248(DP)
阿新 • • 發佈:2018-11-11
題意
給定一個1*n的地圖,在裡面玩2048,每次可以合併相鄰兩個(數值範圍1-40),問最大能合出多少。注意合併後的數值並非加倍而是+1,例如2與2合併後的數值為3。
歪解
記憶化搜尋
設f[l][r][x]表示(l,r)能不能合成x這個數,那麼就有f[l][r][x]|=f[l][k][x-1]&f[k+1][r][x-1]。
然後從大到小列舉x,再列舉個左端點和右端點,判斷一下是否可行。具體實現時,x可以從60開始別問我是怎麼知道的。
程式碼
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=250; int n; int a[maxn]; int f[maxn][maxn][170]; inline bool dfs(int l,int r,int x) { if(f[l][r][x]==1) return true; if(f[l][r][x]==-1) return false; for(int k=l;k<r;k++) if(dfs(l,k,x-1) && dfs(k+1,r,x-1)) return f[l][r][x]=1; f[l][r][x]=-1; return false; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); f[i][i][a[i]]=1; } for(int i=60;i>=0;i--) for(int l=1;l<=n;l++) for(int r=l;r<=n;r++) if(dfs(l,r,i)) { printf("%d\n",i); return 0; } }
偏解
貪心
能合併就合併的策略是顯然的。
對於偶數個數挨在一起的問題是很好解決的;
奇數個數的話要稍微處理一下,讓中間那個數為0(或其它特別的都可以),0兩邊各有一個合併值,意思是要麼選左邊,要麼選右邊。
思路還是蠻好的,可以實現一下。
正解
DP
設f[i][x]表示從i開始往右合成一個x的位置,那麼有DP方程f[i][x]=f[f[i][x-1]][x-1],也就是從i開始兩個x-1合成一個x的意思,不知道為什麼很像倍增公式。
初始化的時候f[i][a[i]]=i+1就OK了。
程式碼
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=250; int n,ans=0; int f[maxn][70]; int main() { int mx=0; scanf("%d",&n); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); f[i][x]=i+1; } for(int j=1;j<=60;j++) for(int i=1;i<=n;i++) { if(f[i][j]==0) f[i][j]=f[f[i][j-1]][j-1]; if(f[i][j]) ans=j; } printf("%d\n",ans); return 0; }