昼夜切换动画

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;
}

posted @ 2021-08-05 14:38  smyslenny  阅读(216)  评论(0编辑  收藏  举报