luogu P3147 USACO16OPEN dp好題
阿新 • • 發佈:2021-08-05
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; }