一. 原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394
二. 题目大意:给一个数列,数列中的数由0到N-1(无序)。每次把数列的第一个数移动到最后一个。求这N个序列中逆序数最小的个数。逆序数就是说对于i<j,若a[i]>a[j],则形成一对逆序数。
三. 解题思路:据说暴力也可以过,只要求出初始的时候的逆序数,然后进行递推。下面介绍线段树解法和递推。
1. 线段树求出初始时的逆序数:在区间为[0,N-1]中,每次读入一个数ele,先查询[ele, N-1]这个区间中有多少个数,再进行单点更新。累积的数就是逆序数的和。时间复杂度O(nlgn)。
2. 递推方法:如果把一个数ele从第一个放到最后一个,比它小的数为ele个,比它大的为N-1-ele个,而这些数全部都在ele后面,因此若当前逆序数为cnt,那么把一个数ele从第一个放到最后一个之后,cnt = cnt-ele+N-1-ele。
四. 代码:
#include <cstdio>
using namespace std;
const int MAX_N = 5100,
INF = 0x3f3f3f3f;
#define LC(t) t<<1
#define RC(t) t<<1|1
struct node
{
int l, r, num;
};
node seTree[4*MAX_N];
int arr[MAX_N];
void build(int l, int r, int nd)
{
seTree[nd].l = l;
seTree[nd].r = r;
seTree[nd].num = 0;
if(l == r)
return;
int mid = l+r >>1;
build(l, mid, LC(nd));
build(mid+1, r, RC(nd));
}
void update(int ele, int nd)
{
int &l = seTree[nd].l,
&r = seTree[nd].r,
&num = seTree[nd].num;
if(ele == l && ele == r){
num++;
return;
}
if(ele < l || ele > r)
return;
int mid = l+r >>1;
if(ele <= mid)
update(ele, LC(nd));
else
update(ele, RC(nd));
num = seTree[LC(nd)].num + seTree[RC(nd)].num;
}
int query(int l, int r, int nd)
{
int &lSide = seTree[nd].l,
&rSide = seTree[nd].r,
&num = seTree[nd].num;
if(l == lSide && r == rSide)
return num;
if(r < lSide || l > rSide)
return 0;
int mid = lSide+rSide >>1;
if(r < mid)
return query(l, r, LC(nd));
if(l > mid)
return query(l, r, RC(nd));
return query(l, mid, LC(nd)) + query(mid+1, r, RC(nd));
}
int main()
{
//freopen("in.txt", "r", stdin);
int N, i, j, cnt, res;
while(~scanf("%d", &N)){
build(0, N-1, 1);
cnt = 0;
for(i = 1; i <= N; i++){
scanf("%d", arr+i);
cnt += query(arr[i], N-1, 1);
update(arr[i], 1);
}
res = cnt;
for(i = 1; i <= N; i++){
cnt = cnt-arr[i]+N-arr[i]-1;
res = res>cnt? cnt:res;
}
printf("%d\n", res);
}
return 0;
}