题目网址http://acm.hdu.edu.cn/showproblem.php?pid=6357
题意: 将一个数字序列中的一段区间翻转,求翻转后的最长非递减序的子串长度,和翻转区间的左右边界。
自己思考了一下,结合了一个简单一点的cf 934C题 题解https://blog.csdn.net/mitsuha_/article/details/79326891
再加上dls的讲解和题解,终于搞懂了这个题目。
题解是这样给的:
加上dls讲解,
对于一个数组,例如 1 2 4 3 3 2 2 5 1 如果我们想得到一个最长序列,其序列其实是一个 1 2 【4 3 2 】5 类似于这种的序列(此处将重复数字去掉)将【】区间翻转即可,但是由于 n = 1e5我们没办法枚举 l r,但是我们可以转换一下思路。对于所有的序列,其实去掉重复部分,其最终形态,是类似于题解中 0 -x-y -9那一长串的的样子。
那我们可以不再枚举l r,进而去枚举所有的x y 值域。
那么我举的例子的答案序列,他所对应的答案x =2 y = 4,序列为 1 2 4 3 2 (4 ) 5 ,(4)是原串中不存在的,我们不统计,但是我们可以由此看出规律:
那就是找到枚举所有的可取 [x ,y] ,然后生成一个匹配串PAT, 0 ,1 , 2, ... ,x ,[ y, y-1, y-2, ... ,x] , y,y+1,..., 9 我们从数组An中去匹配这个串,最大匹配长度就是 最长非递减序的子串长度,[ ]位置对应翻转位置。
在匹配时,dp【i】【j】对应到A数组的 i 位,PAT 的 j 位时当前的最大长度。
所有我们可以看出 dp【i】【j】 = max(dp【i】【j-1】,dp【i-1】【j】+(A[i]==pat[j]? 1 : 0));
更多的细节看代码:
ps:标程的代码真真是骨骼清奇
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = (int)1e5 + 1, maxv = 13;
int t, n, tot, g[maxn][maxv], *f = g[0];
char buf[maxn], pat[maxv];
int main()
{
scanf("%d", &t);
while(t--)
{
scanf("%d%s", &n, buf);
char vL = '9', vR = '0'; //最大数 和 最小数
for(int i = 0; i < n; ++i)
{
vL = min(vL, buf[i]);
vR = max(vR, buf[i]);
}
if(vL == vR)
{
printf("%d %d %d\n", n, 1, 1);
continue;
}
int m = 0;
char pL, pR;
for(char low = vL; low < vR; ++low) //枚举数值,0~9
for(char upp = low + 1; upp <= vR; ++upp)
{
tot = 0;
for(char ch = vL; ch <= low; ++ch) // vl —— low
pat[tot++] = ch;
for(char ch = upp; ch >= low; --ch) // up - low
pat[tot++] = ch;
for(char ch = upp; ch <= vR; ++ch) // up = vr
pat[tot++] = ch;
memset(f, 0, tot * sizeof(int)); // pat 对应一个 vl - low , up - low ,up - vr 的一个序列,显然,up-low是翻转区域
for(int i = 0; i < n; ++i)
for(int j = 0; j < tot; ++j) //dp[i][j] = max(dp[i-1][j]+ buf[i]==pat[j],dp[i-1][j-1]) ,
{
f[j] += buf[i] == pat[j];
if(j && f[j] <f[j-1])
f[j] = f[j-1];
}
if(m < f[tot - 1]) //更新最长区域对应的翻转字符,
{
m = f[tot - 1];
pL = low;
pR = upp;
}
}
tot = 0;
//ans 对应的pat
for(char ch = vL; ch <= pL; ++ch)
pat[tot++] = ch;
for(char ch = pR; ch >= pL; --ch)
pat[tot++] = ch;
for(char ch = pR; ch <= vR; ++ch)
pat[tot++] = ch;
memset(g[0], 0, tot * sizeof(int));//找到最长区域对应的g
for(int i = 0; i < n; ++i)
{
for(int j = 0; j < tot; ++j)
{
g[i+1][j] = g[i][j] + (buf[i] == pat[j]);
if(j && g[i+1][j] <g[i+1][j-1]) g[i+1][j] =g[i+1][j-1];
}
}
//寻找翻转区间
int L = 0, R = 0;
for(int i = n - 1, j = tot - 1; i >= 0; --i)
{
int lft,rht;
lft = g[i][j] + (buf[i] == pat[j]);
if(j!=0) rht = g[i+1][j-1] ;
else rht = -1;
//当前g[i+1][j] 的值,要么从lft转移来,要么从rft转移而来
//如果是从g[i+1][j-1]转移而来的话,需要循环改变j的值
while(lft < rht)
{
// 最长区域为 vl - low [up - low] up - vr 由此可见翻转区域[]的出现位置为low up 相接处
//第一次出现时,i为R,第二次出现时i 为 L-1,因为j对应的是i+1
if(pat[j - 1] == pL && pat[j] == pR)
{
if(!R)
R = i;
else
L = i + 1;
}
--j;
lft = g[i][j] + (buf[i] == pat[j]);
if(j!=0) rht = g[i+1][j-1] ;
else rht = -1;
}
}
printf("%d %d %d\n", m, L + 1, R + 1);
}
return 0;
}
好饿啊,,,