P3147 262144游戏
下面这两道题非常相似,我本来以为可以双倍经验,结果看到数据范围就凉了
P3146传送门
P3147传送门
\(Description\)
在一个\(1*n\)的网格图中,每一个格子有一个权值,相邻两个格子如果权值相同可以合并,权值\(+1\),相当于一维的\(2048\)小游戏,只不过合并后权值不是\(*2\)而是\(+1\)
第一道题\(n<=248\)
第二道题\(n<=26144\)
两道题的输入数据都保证权值\(<=40\)
\(Solution\)
先来说第一道题,数据范围只有\(248\),发现简单的\(n^3\)区间\(DP\)就可以过
定义\(dp[i][j]\)为合并\(i-j\)后的值(必须完全合并,只剩一个数)的最大值,如果不能合并就是\(0\)。
为什么必须全部合并。
因为我们发现假如定义为\(dp[i][j]\)为\(i-j\)合并后的最大值,假设无法完全合并,我们没法记录最大的那个数在左边还是右边。但两个区间合并时需要考虑,必须相邻才能合并,所以这样无法转移,所以不能合并的情况就赋成\(0\),让他不能转移。
所以转移方程就显然了
for(re int i=1;i<=n;++i) dp[i][i]=a[i],ans=max(dp[i][i],ans);//不要忘记赋初始值
for(re int i=n;i>=1;--i)
for(re int j=i+1;j<=n;++j)
for(re int k=i;k<=j;++k)//倒序枚举法,保证所有区间的小区间都被更新过
{
if(dp[i][k]==dp[k+1][j])//假如合并后相等
dp[i][j]=max(dp[i][j],dp[i][k]+1);//题意
ans=max(ans,dp[i][j]);
}
下面是第二个题(数据范围到达\(2e5\)时的做法)
第一种做法显然会\(TLE\)飞,考虑换一种状态设计方法
\(dp[i][j]\)表示:左端点为\(j\),合并出权值\(i\)需要到达的右边界**(注意是边界,也就是合并的最右边一个再\(+1\))*
那么转移方程就是
\(dp[i][j]=dp[i-1][dp[i-1][j]]\)
有点类似倍增的思想,先合并出\(i-1\),再到结束的地方合出另一个\(i-1\),合起来就是\(i\)
边界条件:\(dp[a[i]][i]=i+1\)
转移方程先枚举要凑的数\(i\),再枚举当前左边界\(j\),如果能找到右边界说明能凑出来这个数,\(ans=max(ans,i)\)即可
一个问题是枚举要凑的数最多枚举到多少,发现每次\(+1\)需要合并两个,类似于\(2048\)游戏,最大合并的值应该是\(log_2^{262144}=18\),再加上最大初始值\(40+18=58\),所以只用枚举到\(58\)即可
还有\(DP\)状态很奇葩注意数组大小开对,不然\(RE\)到飞
int n,x,f[62][270007],ans;
int main()
{
n=read();
for(re int i=1;i<=n;++i) x=read(),f[x][i]=i+1;
for(re int i=2;i<=58;++i)
{
for(re int j=1;j<=n;++j)
{
if(!f[i][j])
f[i][j]=f[i-1][f[i-1][j]];
if(f[i][j])
ans=i;
}
}
printf("%d\n",ans);
return 0;
}
总结
看来倍增思想对于区间\(DP\)也有很大优化作用,一般可以用倍增思想处理的是合并值有规律可以用下标存储的问题,\(dp\)数组存储的是右边界(右端点\(+1\))