题目链接
https://www.luogu.com.cn/problem/P7714
题目简述
题目描述
小 E 给你一个长度为 n 的排列 p1,p2,⋯,pn。小 E 想要把它排序。
小 E 每次可以花区间长度,即 r−l+1 的代价,选择排列中的任意一段区间 [l,r],并将 [l,r] 从小到大排序。
现在你可以让他进行若干次这个操作,直到 p 中元素的值从 1 到 n 按升序排序,即对于 1 到 n 的每一个 i,都有pi=i。
小 E 问你,他花的代价最少为多少?
输入格式
本题有多组询问,第一行有一个数 T 表示询问组数。
对于每组询问:
第一行给出一个整数 n。
第二行 n 个整数,由空格隔开,代表排列 p 中元素的值。
输出格式
T 行,每行一个整数表示一组询问的答案。
输入输出样例
输入
2 3 1 3 2 4 3 2 1 4
输出
2 3
说明/提示
【样例 11 说明】
对于第一组数据,可选择区间 [2,3] 进行排序。
对于第二组数据,可选择区间 [1,3] 进行排序。
【数据规模与约定】
对于 20% 的数据,n≤4。
对于另 30% 的数据,∑n≤5000。
对于另 10% 的数据,p1=n。
对于 100% 的数据,1≤T,∑n≤106。
思路分析
因为题目说花区间长度的代价去还原1到n的升序排列,一开始我认为只要用双指针l和r分别从1和n开始向里层移动搜索,只要发现p[l]!=l的最左位置和p[r]!=r的最右位置,然后再r-l+1就可以了,但提交后发现只通过了一部分样例,后来注意到题目说对于一个排列,可以使用若干次上述操作,那么也就是说我们忽略了下图的情况
1 3 4 2 5 7 6 8
1 2 3 4 5 6 7 8
以5为界,3,2,4为一个区间,7,6为一个区间,那么只需花费5个代价,但是依据我们的算法操作,答案却是6。
所以对撞指针(相反方向)应该是无法解决的,因为我们可以从上述例子看出这些需要修改的区间大多会被划分成一个个小区间。所以可以采用双指针的另一种类型快慢指针(相同方向)去从左到右检索出每一个区间。
那么首先定义一个指针l(这里并不是使用真正的指针,而是只要能够指向索引下标的变量就可以了),从1开始搜索,如果发现p[l]==l就继续向右移动;
当发现p[l]!=l时说明存在一个需要修正的区间,那么定义另一个指针r=l+1;此时l与r包围成了一个区间, 那么区间最左端已经确定,就是l所在位置,那么现在只需要确定区间最右端位置。那么只有当l与r区间的p[i](p[i]指的是p排列里的数值)的最大值maxp与r指针所指向下标相同时,便能构成一个最优代价区间(例如一个区间内最大数值为100,那么要更改的区间的最右端一定要延伸到r指针指向下标100的地方,因为要满足p[i]==i。也就是能满足r=maxp=100,但不用到101,因为若下标指向101,那么区间最大值也要是101,不用扩大区间,可以将101放到下一个需要排序的区间里)
代码贴这
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int t,n;
cin>>t;
for(int i=1;i<=t;i++)
{
int sum=0;
cin>>n;
int*p=new int[n+1];
p[0]=0;
for(int j=1;j<=n;j++)
{
cin>>p[j];
}
int l=0,r=0,maxp=0;
while(l<n)
{
if(p[l]==l)
{
l++;
}
else
{
r=l+1;
maxp=max(p[l],p[r]);
while(r<maxp)
{
r++;
maxp=max(p[r],maxp);
}
sum+=(r-l+1);
l=r+1;
}
}
cout<<sum<<endl;
}
return 0;
}
有什么不足之处请大家多多指教☺