zoukankan      html  css  js  c++  java
  • P3147 262144游戏

    下面这两道题非常相似,我本来以为可以双倍经验,结果看到数据范围就凉了 [P3146传送门](https://www.luogu.org/problem/P3146) [P3147传送门](https://www.luogu.org/problem/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 ",ans); return 0; } ``` #总结 看来倍增思想对于区间$DP$也有很大优化作用,一般可以用倍增思想处理的是合并值有规律可以用下标存储的问题,$dp$数组存储的是右边界(右端点$+1$)
  • 相关阅读:
    关于闭包的一些知识
    浏览器解析JavaScript原理(1)
    函数作用域及函数表达式
    jquery
    前端常用插件
    Git及GitHub
    angular框架
    express
    ES6基础
    Node.js相关总结
  • 原文地址:https://www.cnblogs.com/Liuz8848/p/11720569.html
Copyright © 2011-2022 走看看