http://acm.hdu.edu.cn/showproblem.php?pid=6357
题意:给定一个长度为n只包含‘0’~‘9’的字符串,你可以翻转[l,r]的区间一次,然后求它的最长不下降子序列的长度的最大值,并求出翻转区间。
思路:
我们可以构造一个b序列为0123456789,然后让a序列和b序列来匹配,b可以重复匹配,这样求出来的最长公共序列就是最长不下降序列,然后序列可以翻转一次。我们发现如果在a序列中枚举翻转端点是很难实现的。但可以在b序列上枚举翻转端点(最多C(10,2)种方案)。
换句话说,我们可以枚举翻转的两个端点的值。
然后,b序列可以转化成这个样子:
假设我们枚举的翻转的左端点值为y,右端点值为x,满足x<y
b序列就可以变成:
0,1,2,……x−1,x,(y,y−1,y−2,……,x+1,x),y,y+1,……8,9,注意翻转的左右两个端点要记录两次,然后枚举翻转区间找到最大值,即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
#define ll long long
int dp[maxn][22],L[maxn][22],R[maxn][22];
char a[maxn];
int b[maxn];
int c[maxn];
int n,cnt,tl,tr,ans,ansl,ansr,ma,mi;
int work()
{
for(int i=1;i<=n;i++)
{
for(int j=0;j<cnt;j++)
{
L[i][j]=0,R[i][j]=0;
}
}
for(int i=0;i<cnt;i++)
dp[0][i]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<cnt;j++)
{
dp[i][j]=dp[i-1][j];
L[i][j]=L[i-1][j];
R[i][j]=R[i-1][j];
if(b[i]==c[j])
{
dp[i][j]++;
if(tl==j&&!L[i][j])
L[i][j]=i;
if(tr==j)
R[i][j]=i;
}
if(dp[i][j-1]>dp[i][j]&&j>=1)
{
dp[i][j]=dp[i][j-1];
L[i][j]=L[i][j-1];
R[i][j]=R[i][j-1];
}
}
}
return dp[n][cnt-1];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
scanf("%s",a+1);
ma=0;
mi=9;
for(int i=1;i<=n;i++)
{
b[i]=a[i]-'0';
ma=max(b[i],ma);
mi=min(b[i],mi);
}
cnt=0;
for(int i=0;i<10;i++)
c[cnt++]=i;
int ans=work();
ansl=1;
ansr=1;
for(int i=mi;i<=ma;i++)
{
for(int j=mi;j<i;j++)
{
cnt=0;
for(int k=0;k<=j;k++)
c[cnt++]=k;
tl=cnt;
for(int k=i;k>=j;k--)
c[cnt++]=k;
tr=cnt-1;
for(int k=i;k<10;k++)
c[cnt++]=k;
int ans1=work();
if(ans1>ans&&L[n][cnt-1]&&R[n][cnt-1])
{
ans=ans1;
ansl=L[n][cnt-1];
ansr=R[n][cnt-1];
}
}
}
printf("%d %d %d\n",ans,ansl,ansr);
}
return 0;
}