目录
题目描述
小 E 给你一个长度为 n 的排列 。小 E 想要把它排序。
小 E 每次可以花区间长度,即 r−l+1 的代价,选择排列中的任意一段区间 [l,r],并将 [l,r] 从小到大排序。
现在你可以让他进行若干次这个操作,直到 p 中元素的值从 1到 n 按升序排序,即对于 1 到 n 的每一个 i,都有 。
小 E 问你,他花的代价最少为多少?
输入
本题有多组询问,第一行有一个数 TT 表示询问组数。
对于每组询问:
第一行给出一个整数 n。
第二行 n 个整数,由空格隔开,代表排列 p 中元素的值。
输出
T行,每行一个整数表示一组询问的答案。
样例
输入数据 1
2
3
1 3 2
4
3 2 1 4
输出数据 1
2
3
提示
分析
1)首先需要理解题目需要我们干什么,得到什么用的结果。
随便举个例子:1,3,4,6,5,2,8,7
第一种比较简单的方法就是直接选择区间【2,8】进行从小到大排序,通过付出代价8-2+1=7,就能实现从小到大的排序。但是这是不是最小的代价呢,我们再考察考察。题目要求的是最小的代价是多少?这明显也是个贪心问题。
从上面数据表来看,我们发现p[1]=1,p[5]=5也就是这两个位置的数据已经对应上了,是不需要再排序了。而上面对【2,8】区间的排序,自然就把p[5]算上去了,把不必要排的也排了,所以不是最优解。
第二种方法,分区间排序
先对区间【2,4】进行从小到大排序,这样代价=4-2+1=3
然后再对区间【6,8】进行从小到大排序,付出代价8-6+1=3
最终总代价=3+3=6,小于第一种方法的代价7。
但是对于下面这种情况又该怎么办呢
这样的话,P[5]也是必须参与排序才能实现从小到大的排序。代价至少为7
2)思路
通过上面的实例分析,仔细观察可以发现就可以通过L,R游标(相当于指针)来游动。当P[i]!=i的时候,可以令L=i,然后令R=i+1,R++开始向右滑动。同时不断维护区间的最大值max=P[],直到R=max(因为要想对区间的数进行从小到大排序,那么一定是要考虑到最大值刚好能排在区间的顶部才行。)
然后在将L从R+1,R++开始继续往下重复上述的滑动寻找。同时将每个区间的代价累加起来。
例如:1 3 4 2 5 8 7 6
区间[2,4]的最大值为4,而区间的上限刚好为4,所以可以对此区间进行排序。然后L转到4+1=5的位置开始继续寻找下一个区间。因为p[5]=5,所以p[5]不需要排序,L更新为6,继续寻找,刚好p[6]!=6,所以又开始新一个区间的左端点,R=L+1,R++继续滑动,最后[6,8]区间的最大值为8,而区间上限刚好也是8,所以可以对此区间进行从小到大排序。
例如:1 3 4 8 5 2 7 6
如果是这组数,我们发现左端点为L=2,右端点R=L+1,R++往右滑动,而区间的最大值为8,所以只有对区间[2,8]的数进行排序才有可能实现从小到大排序。最终代价为8-2+1=7
Code
#include <bits/stdc++.h>
using namespace std;
int a[1000005];
int main()
{
int T;
cin >> T;
while (T--)
{
int n, ans = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
int L = 1;
while (L <= n)
{
if (a[L] == L) // 不需要排序
L++;//更新左端点
else
{
int max_num = a[L]; //区间最大值
int R = L + 1;
max_num = max(max_num, a[R]); //维护区间最大值
while (max_num > R)
{
R++;//右端点往右滑动
max_num = max(max_num, a[R]);//维护区间最大值
}
ans += R - L + 1; //累计代价
L = R + 1; //更新左端点
}
}
cout << ans << endl;
}
return 0;
}