题意:给一串数(长度<=1e5),翻转其中的一段区间,使得翻转后这串数的最长不下降子序列的长度最大。求最大的长度以及翻转的区间的左右端点。
明显不可能遍历所有的子区间……但是里面的每个数都只有0到9,因此可以枚举翻转的区间的左右端点的值。例如左端点的值是L,右端点的值是R,那么求出原串和0,1,2,…,L,(R,R-1,..,L),R,R+1,…,9这个模式串的LCS。维护最大长度就可以得到答案。
比较麻烦的是怎么求翻转区间的左右端点。我们记录上面说的模式串中翻转位置的左右端点。在LCS处理到这两个端点的值的时候考虑如何转移。可以通过 L[i][j] L [ i ] [ j ] 和 R[i][j] R [ i ] [ j ] 两个数组来维护翻转区间的最左端点和最右端点。如果a[i]==b[j],那么i就是翻转区间的端点。否则的话就是从另一个状态转移过来的,直接继承那个状态的值。
注意在更新的时候还要判断左右端点是否存在……
附赠一道这题的简化版本:Codeforces Round #462 (Div. 2): C. A Twisty Movement
#include <bits/stdc++.h>
using namespace std;
const int maxn=100000+5;
char s[maxn];
int T,n,a[maxn],b[15],dp[maxn][15],L[maxn][15],R[maxn][15],spl=0,spr=0;
void lcs(int len1,int len2){
// dp[i][j]=max(dp[i-1][j]+(a[i]==b[j]),dp[i][j-1]);
for (int i=1;i<=len2;i++){
dp[0][i]=0;
}
for (int i=1;i<=len1;i++){
for (int j=1;j<=len2;j++){
dp[i][j]=dp[i-1][j];
L[i][j]=L[i-1][j];
R[i][j]=R[i-1][j];
if (a[i]==b[j]){
dp[i][j]++;
if (L[i][j]==0&&j==spl){
L[i][j]=i;
}
if (j==spr){
R[i][j]=i;
}
}
if (dp[i][j-1]>dp[i][j]){
dp[i][j]=dp[i][j-1];
L[i][j]=L[i][j-1];
R[i][j]=R[i][j-1];
}
}
}
}
void show(int len1,int len2){
printf("a:\n");
for (int i=1;i<=len1;i++){
printf("%d ",a[i]);
}
printf("\nb:\n");
for (int i=1;i<=len2;i++){
printf("%d ",b[i]);
}
printf("\n");
}
int main(){
#ifdef __APPLE__
//freopen("1.in","r",stdin);
#endif
scanf("%d",&T);
while (T--){
scanf("%d",&n);
scanf("%s",s+1);
for (int i=1;i<=n;i++){
a[i]=s[i]-'0';
}
int len1=n,len2=0,ans=0,ll=0,rr=0;
for (int i=0;i<=9;i++) b[++len2]=i;
lcs(len1,len2);
ans=dp[len1][len2],ll=1,rr=1;
for (int l=0;l<=9;l++){
for (int r=l+1;r<=9;r++){
len2=0;
if (l==r) continue;
for (int i=0;i<=l;i++) b[++len2]=i;
spl=len2+1;
for (int i=r;i>=l;i--) b[++len2]=i;
spr=len2;
for (int i=r;i<=9;i++) b[++len2]=i;
// spl=l+2,spr=r+2;
lcs(len1,len2);
if (dp[len1][len2]>ans&&L[len1][len2]&&R[len1][len2]){
ans=dp[len1][len2];
ll=L[len1][len2];
rr=R[len1][len2];
}
}
}
if (n==1)
ll=rr=1;
printf("%d %d %d\n",ans,ll,rr);
}
return 0;
}