觉得开始需要着手练下线段树了,所以从单点更新开始着手。
题目链接:HDU 1394.Minimum Inversion Number
题目大意:给定一个长度为n的序列,然后每次将序列当中的第一个数放在序列末尾,这样我们一共会得到n个序列。每个序列都会有个逆序数,取这n个数当中的最小的作为结果输出。
最最暴力的方法是把这n种情况排列出来,然后每种情况都算一下逆序数,然后取最小。
由于暴力算法计算一组情况逆序数的时间复杂度是O(n^2)的,n为小于等于5000的数,所以我们可以考虑下能否用一组逆序数推出其他所有情况逆序数的结果呢?由于本题的特殊性,是可以做到的,首先n个数中分别是[0, n - 1]当中的数字而且无重复数字出现,那么我们假设已知的某一序列的逆序数为sum,那么它的下一个序列式将a0放到队尾,那么序列中的其他数字必然都在它的前面(包括a0个比它小的数和n - a0 - 1个比它大的数),也就是说逆序数sum会减少a0个,增加n - a0 - 1个。
即sum = sum - (a0) + (n - a0 - 1);
暴力O(n^2)做法代码:(218ms)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[5010];
int n;
int main() {
while(~scanf("%d", &n)) {
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
int ans = 1e9;
int x = 0;
for(int i = 0; i < n; i++) {
for(int j = i + 1; j < n; j++) {
if(a[i] > a[j]) x++;
}
}
for(int i = 0; i < n; i++) {
x = x + (n - a[i] - 1) - (a[i]);
ans = min(ans, x);
}
printf("%d\n", ans);
}
return 0;
}
线段树做法的核心部分和暴力法相同,也是只构建一棵空树,按照输入的顺序更新和查询加和。
线段树:(78ms) 刚开始写,线段树的格式还有待学习和习惯,虽然格式被吐槽了。。不过还是觉得封装起来舒服
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAX 5010
#define LL(x) (x << 1)
#define RR(x) (x << 1 | 1)
#define MID(x, y) (x + ((y - x) >> 1))
int n;
int a[MAX];
class TNode {
public:
int l, r, num;
int mid() {return MID(l, r);}
void init() {num = 0; return ;}
};
class segment_tree {
public:
TNode tree[4 * MAX];
void build(int l, int r, int t) {
tree[t].l = l, tree[t].r = r;
tree[t].init();
if(l != r) {
int mid = tree[t].mid();
build(l, mid, LL(t));
build(mid + 1, r, RR(t));
}
}
void update(int pos, int t) {
int l = tree[t].l, r = tree[t].r;
if(l == r) tree[t].num++;
else {
int mid = tree[t].mid();
if(pos <= mid) update(pos, LL(t));
else update(pos, RR(t));
tree[t].num = tree[LL(t)].num + tree[RR(t)].num;
}
}
int query(int st, int ed, int t) {
int l = tree[t].l, r= tree[t].r;
if(st <= l && ed >= r) return tree[t].num;
else {
int mid = tree[t].mid();
int num1 = 0, num2 = 0;
if(st <= mid) num1 = query(st, ed, LL(t));
if(ed > mid) num2 = query(st, ed, RR(t));
return num1 + num2;
}
}
}seg;
int main() {
while(~scanf("%d", &n)) {
seg.build(0, n - 1, 1);
int sum = 0;
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
seg.update(a[i], 1);
if(a[i] != n - 1) sum += seg.query(a[i] + 1, n - 1, 1);
}
int ans = 1e9;
for(int i = 1; i <= n; i++) {
sum = sum + (n - a[i] - 1) - (a[i]);
ans = min(ans, sum);
}
printf("%d\n", ans);
}
return 0;
}