[题解]P3147 [USACO16OPEN] 262144 P
P3146 [USACO16OPEN] 248 G(弱化版)
我们先考虑区间dp,设\(f[i][j]\)表示\(a[i,j]\)恰好合并成\(1\)个元素后的答案,\(0\)表示无法合并。
那么对于\(k\)使得\(f[i][k]=f[k+1][j]\neq 0\),有转移\(f[i][j]=f[i][k]+1\)。
有结论:对于可以合并的区间\([l,r]\),它们能合并出的答案是唯一的。
\(\bf{Proof}\):可以将每个值\(a[i]\)映射到\(b[i]=2^{a[i]}\),合并相当于同指数相加,所以\(b\)的最终合并答案固定为\(2^x\),那么\(a\)的答案固定为\(x\)。
根据此结论,只要找到一个\(k\)满足条件,直接更新并break
出去就可以,不需要对所有\(k\)取\(\max\)。
时间复杂度\(O(n^3)\),可以通过弱化版。
点击查看代码
#include<bits/stdc++.h>
#define N 250
using namespace std;
int n,a[N],f[N][N],ans;
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],f[i][i]=a[i];
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
if(f[i][k]==f[k+1][j]&&f[i][k]){
f[i][j]=f[i][k]+1;
break;
}
}
ans=max(ans,f[i][j]);
}
}
cout<<ans<<"\n";
return 0;
}
根据上面的结论,可以推得:对于一个左端点\(l\),如果存在右端点\(r\),使得\(a[l,r]\)可以合并成整数\(k\),那么这个右端点\(r\)是唯一的。又受上面用二进制思考的证明的启发,我们采用倍增。
设\(f[i][j]\)表示以\(i\)为左端点合并成\(j\)这个答案的右端点\(+1\)是多少,\(0\)表示不存在右端点。
有转移\(f[i][j]=f[f[i][j-1]][j-1]\),很纯的倍增。
代码中循环跑到了\(58\),是因为答案最大就是\(58\),因为\(262144=2^{18}\),最极端的情况就是所有元素都是\(40\),此时答案是\(40+18=58\)。所以时间复杂度是\(O(n(\log n+V))\)的。
点击查看代码
#include<bits/stdc++.h>
#define N 262150
#define V 60
using namespace std;
int n,a[N],f[N][V],ans;
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],f[i][a[i]]=i+1;
for(int i=2;i<=58;i++){
for(int j=1;j<=n;j++){
if(!f[j][i]) f[j][i]=f[f[j][i-1]][i-1];
if(f[j][i]) ans=i;
}
}
cout<<ans<<"\n";
return 0;
}