休 息 ( r e s t ) 休息(rest) 休息(rest)
题目链接: j z o j 3462 jzoj\ 3462 jzoj 3462
题目
休息的时候,可以放松放松浑身的肌肉,打扫打扫卫生,感觉很舒服。在某一天,某 L M Z LMZ LMZ 开始整理他那书架。已知他的书有 n n n 本,从左到右按顺序排列。他想把书从矮到高排好序,而每一本书都有一个独一无二的高度 H i H_i Hi。他排序的方法是:每一次将所有的书划分为尽量少的连续部分,使得每一部分的书的高度都是单调下降,然后将其中所有不少于 2 2 2 本书的区间全部翻转。重复执行以上操作,最后使得书的高度全部单调上升。可是毕竟是休息时间, L M Z LMZ LMZ 不想花太多时间在给书排序这种事上面。因此他划分并翻转完第一次书之后,他想计算,他一共执行了多少次翻转操作才能把所有的书排好序。 L M Z LMZ LMZ 惊奇地发现,第一次排序之前,他第一次划分出来的所有区间的长度都是偶数。
输入
第一行一个正整数 n n n, 为书的总数。
接下来一行 n n n个数,第 i i i个正整数 H i H_i Hi,为第 i i i 本书的高度。
输出
仅一个整数,为 L M Z LMZ LMZ 需要做的翻转操作的次数。
样例输入
6
5 3 2 1 6 4
样例输出
3
样例解释
第一次划分之后,翻转 ( 5 , 3 , 2 , 1 ) (5,3,2,1) (5,3,2,1), ( 6 , 4 ) (6,4) (6,4)。之后,书的高度为 1 2 3 5 4 6 1\ 2\ 3\ 5\ 4\ 6 1 2 3 5 4 6,然后便是翻转 ( 5 , 4 ) (5,4) (5,4)即可。
数据范围
对于
10
%
10\%
10%的数据:
n
<
=
50
n\!<=\!50
n<=50
对于
40
%
40\%
40%的数据:
n
<
=
3000
n\!<=\!3000
n<=3000
对于
100
%
100\%
100%的数据:
1
<
=
n
<
=
100000
,
1
<
=
H
i
<
=
n
1\!<=\!n\!<=\!100000\ ,\ 1\!<=\!H_i\!<=\!n
1<=n<=100000 , 1<=Hi<=n
思路
这道题其实挺结论题的,但是又要用到树状数组。
首先第一次,我们就按照正常的方式来模拟。
接着,我们就只要用树状数组的方式求出当前数组的逆序对个数,加上原来模拟时算出的个数,就是答案了。
至于为什么是逆序对,就是因为一开始模拟玩之后几个交换的区块都整齐了,就只有区块与区块之间的不整齐。那我们就要不停的交换临近的两个数,让那些不整齐的数交换。
那就是逆序对个数咯!
代码
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
struct node {
ll x, num;
}a[200000];
ll c[200000], n, ans;
int cmp(node x, node y) {
return x.x > y.x;
}
void add(int x) {//单点加值
while (x <= n) {
c[x]++;
x += x & -x;
}
}
int getsum(int x) {//单点求值
int re = 0;
while (x > 0) {
re += c[x];
x -= x & -x;
}
return re;
}
int main() {
scanf("%d", &n);//读入
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i].x);//读入
a[i].num = i;//记录坐标
}
for (int i = 1; i < n; i++) {//第一次直接暴力搞
int j = i, k;
while (a[i].x > a[i + 1].x && i < n) i++;
k = i;
bool yes = 0;
while (j < k) {
yes = 1;
swap(a[j].x, a[k].x);
j++;
k--;
}
if (yes) ans++;
}
sort(a + 1, a + n + 1, cmp);//排序
for (int i = 1; i <= n; i++) {//求逆序对
ans += getsum(a[i].num);
add(a[i].num);
}
printf("%lld", ans);//输出
}