*题解:P3147 [USACO16OPEN] 262144 P
解析
对于弱化版,设 \(f_{l,r}\) 表示 \([l,r]\) 之间的数可以合成出的数是什么,枚举断点即可轻松转移。
考虑两个三元组 \((l,k,x)\) 与 \((k + 1,r,x)\)。前两个数表示一个区间,第三个数表示这个区间能够合成出来的数,那么两个区间合并之后,形成一个 \((l,r,x + 1)\)。我们先前的做法,是将状态维度设为 \(l\) 和 \(r\),状态值设为 \(x\)。但实际上可以调换一下。设 \(f_{l,x}\) 表示以 \(l\) 为左端点合成出 \(x\) 的区间右端点的后继(即左闭右开)。于是有形如倍增求 LCA 的转移:
\[\large f_{l,x} = f_{f_{l,x - 1},x - 1}
\]
时间复杂度 \(O(NV)\)。其中 \(V\) 为值域。对于其具体的值,考虑能合成出的最大数,其对应着 \(N=262144 = 2^{18}\),所有数字均为 \(40\)。每轮都可以和相邻的数字合成使值加一,同时 \(N\) 减半,最多减半 \(\log_2 N=18\) 次,故最多进行 \(18\) 次加一变为 \(58\)。故 \(V=58\)。
代码
/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 270000,M = 60,L = 19,L2 = 17,mod = 998244353;
int f[N][M];
int a[N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
f[i][a[i]] = i + 1;
}
int res = 0;
for(int i=1;i<M;i++){
for(int j=1;j<=n;j++){
f[j][i] = max(f[j][i],f[f[j][i - 1]][i - 1]);
if(f[j][i]){
res = max(res,i);
}
}
}
cout<<res;
return 0;
}

浙公网安备 33010602011771号