1. 程式人生 > >洛谷3146 [USACO16OPEN]248(DP)

洛谷3146 [USACO16OPEN]248(DP)

題意

給定一個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;
}