题目意思是给出一个数n,然后给出0~n-1中不同的n的个数组成一个序列,然后每次将最后一个数放到最后一个,然后看这样一次次转换的序列中的
逆序树的最小值。
求逆序树有好多种方法:暴力法,归并排序法,树状数组法,线段树法。最近学习线段树,所以就用线段树写,其他方法后续会补上。
这里对于题目所给序列。对于第一个数x,则后面必然有x-1~0这几个数都比它小,则逆序对为x,后面的x+1~n-1都比x大是正序的,无影响。
当吧x挪到最后一位时,本来x-1~0这几个数是在x后面,则现在变成是在前面,则当前逆序数减去x对,原来的x+1~n-1都再x的前面了,则
逆序数对又增加了n-1-x个,假如当前序列逆序数为num,则将第一位x挪到最后一位,序列的逆序数变为 num - x + n - 1 - x。
则序列变换时,求逆序数的时间复杂度为线性阶O(n)。时间复杂度已经不能再减少了,这时可以通过线段树来优化求原始序列的逆序数的
时间复杂度,首先构建一个空的线段树,里面不含有任何元素,当输入一个数x[i]时,现在线段树中查找比x大的元素的个数,当前在线段
树中的元素其在序列中的下标都比x[i]对应的下标小,则查找出的元素个数,是数x[i]的逆序对数量,然后再将新元素插入线段树。
AC代码:
#include<iostream>
#include<stdio.h>
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1
using namespace std;
const int maxn = 5010;
/**使用线段树求逆序树**/
int sum[maxn<<2];
int x[maxn];
void push_up(int root)
{
sum[root] = sum[root<<1] + sum[root<<1|1];
}
void build(int left,int right,int root)
{
sum[root] = 0; //最开始每个节点sum都是0
if(left == right)
{
return;
}
int mid = (left+right)>>1;
build(lchild);
build(rchild);
}
//将新输入的树插入线段树
void Insert(int x,int left,int right,int root)
{
if(left==right)
{
sum[root]++;
return;
}
int mid = (left+right)>>1;
if(x<=mid) Insert(x,lchild); //插入左子树
else Insert(x,rchild); //插入右子树
push_up(root);
}
//当输入一个数x时,查找线段树中x~n-1范围中数的个数
int query(int L,int R,int left,int right,int root)
{
if(L<=left && right<=R)
{
return sum[root];
}
int ans = 0;
int mid = (left+right)>>1;
if(L<=mid) ans += query(L,R,lchild);
if(R>mid) ans += query(L,R,rchild);
return ans;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
int num = 0;
build(0,n-1,1); ///构建空的线段树
///用线段树求逆序数
for(int i = 0; i < n; i++)
{
scanf("%d",&x[i]);
num += query(x[i],n-1,0,n-1,1);
Insert(x[i],0,n-1,1);
}
int ans = num;
for(int i = 0; i < n; i++)
{
num = num+n-1-2*x[i];
ans = min(ans,num);
}
printf("%d\n",ans);
}
return 0;
}