1. 程式人生 > 其它 >luogu P3147 USACO16OPEN dp好題

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

本欲起身離紅塵,奈何影子落人間。