题意
给定一个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;
}