luogu P3147 USACO16OPEN dp好题
luogu P3147 [USACO16OPEN]262144 P
题意:
给出 n 个正整数,\((2 \leq n \leq 262144)\),范围在 \(1- 40\) 内,选择相邻的两个相同的数,然后合并成一个比原来的大一的数,使得最大的数最大。
如果不看数据范围的话,就是一道区间dp的板子题,但是我们发现 \(n\) 太大了,直接这么开二维数组是不行的,考虑缩小数组或者压缩状态。
这里用到了倍增的思想来将第一维缩小。
设 \(f[i][j]\) 表示左端点为 \(j\) 能够合并成 \(i\) 的右端点的位置。
状态转移方程为:
\(f[i][j]=max(f[i-1][f[i-1][j]])\)
解释一下,我们先找到以 \(j\) 为左端点,能合并到 \(i-1\) 的位置,然后以这个位置为左端点,再合并 \(i-1\) 的位置,将这两个合并,就能合并出 \(i\) 了。
为什么值要枚举到 \(58\) 呢
我们发现 \(2^{18}=262144\)
所以值最大只有 \(58(40+18)\),然后这道题就能做了。
/*
*@Author:smyslenny
*@Date: 2021.08.05
*@Title: P3147 [USACO16OPEN]262144 P
*@Main idea:f[i][j] 表示左端点为 j 能合并出 i 这个数的右端点的位置
f[i][j]=f[i-1][f[i-1][j]]
*/
#include <bits/stdc+.h>
using namespace std;
const int M=3e5+5;
int f[60][M],Ans,n;
inline int read()
{
register int x=0,y=1;
register char c=getchar();
while(!isdigit(c)) {if(c=='-') y=0;c=getchar();}
while(isdigit(c)) {x=x*10+(c^48);c=getchar();}
return y?x:-x;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
f[read()][i]=i+1;
for(int i=2;i<=58;i++)
{
for(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;
}
本欲起身离红尘,奈何影子落人间。