http://acm.hdu.edu.cn/showproblem.php?pid=6357
题意
给一个数值范围为0-9的a数组,可以选择翻转一个区间,问非严格最长上升子序列,以及翻转的区间。
分析
官方题解的做法:
它把最长不下降子序列映射成两个序列的最长公共子序列问题
a序列就是给出的原序列
b序列是值域的序列
需要注意的是:b序列可以重复匹配
一般的最长不下降子序列中,b序列就是:0,1,2,3,4,5,6,7,8,9
这样a和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
其中括号内的部分可以通过翻转使得整个串仍然是一个0到9的不下降序列。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define MAXN 100010 using namespace std; int n,t; char s[MAXN]; int b[MAXN],spl,spr,ansl,ansr; int dp[MAXN][22],tl[MAXN][22],tr[MAXN][22];; int solve(int cnt){ 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]; tl[i][j]=tl[i-1][j]; tr[i][j]=tr[i-1][j]; if(s[i]==b[j]){ dp[i][j]++; if(spl==j&&tl[i][j]==0) tl[i][j]=i; if(spr==j) tr[i][j]=i; } if(dp[i][j-1]>dp[i][j]){ dp[i][j]=dp[i][j-1]; tl[i][j]=tl[i][j-1]; tr[i][j]=tr[i][j-1]; } } return dp[n][cnt-1]; } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%d",&n); scanf("%s",s+1); int minl=9; int maxl=0; for(int i=1;i<=n;i++){ s[i]-='0'; maxl=max(maxl,int(s[i])); minl=min(minl,int(s[i])); } for(int i=0;i<10;i++) b[i]=i; int ans=solve(10); ansl=1; ansr=1; for(int l=minl;l<=maxl;l++) for(int r=minl;r<l;r++){ int cnt=0; for(int i=0;i<=r;i++) b[cnt++]=i; spl=cnt; for(int i=l;i>=r;i--) b[cnt++]=i; spr=cnt-1; for(int i=l;i<10;i++) b[cnt++]=i; int ans1=solve(cnt); if(ans1>ans&&tl[n][cnt-1]&&tr[n][cnt-1]){ ans=ans1; ansl=tl[n][cnt-1]; ansr=tr[n][cnt-1]; } } printf("%d %d %d ",ans,ansl,ansr); } return 0; }