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\)

posted @ 2019-10-22 16:26  __Liuz  阅读(134)  评论(0编辑  收藏  举报