//第一次做线段树。。。要加油了orz
题意
有0~n-1数字组成的序列,然后进行这样的操作,每次将最前面一个元素
放到最后面去会得到一个序列,每得到一个序列都可得出该序列的逆序数
(如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,
那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数)
要求求出最小的逆序数。
题解:(线段树)//注:题解有参考
1、对于某一序列,其中的某一个数a[i]能构成多少个逆序,只须判断
在a[i]+1~n的范围内找之前的数是否出现过的次数;
2、然后求出第一个序列的逆序数。
3、由第一个序列的逆序数可以推出它下一个序列的逆序数。
规律:下一个序列的逆序数 sum = sum + n - a[i] - 1 - a[i];
解释:比如说:序列 3 6 9 0 8 5 7 4 2 1
把3移到后面,则它的逆序数会减少3个(0 2 1)
但同时会增加 n -a[i] - 1个。
就这样求出第一个序列的逆序数就可以得到后面变化后序列的逆序数了
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ls l,m,i<<1//左孩子
#define rs m+1,r,i<<1|1//右孩子
#define L(i) ((i)<<1)
#define R(i) ((i)<<1|1)
struct treenode
{
int l,r,val;//val存放在l-r区间内数的个数
}tree[1000006];
int val[1000006];
void build(int l,int r,int i)//建树
{
tree[i].val=0;
tree[i].l=l;//左右范围都是要记录的便于查找
tree[i].r=r;
//当l==r说明这个区间的数只有一个了,不能再分下去了,此时结束分树
if(l==r) return;
int m=(l+r)>>1;//否则,区间至少有2个数,可以再分下去
build(ls);
build(rs);
}
int query(int l,int r,int i)
{
if(tree[i].l==l&&tree[i].r==r) return tree[i].val;
int m=(tree[i].l+tree[i].r)>>1;
if(r<=m) return query(l,r,L(i));
else if(l>m) return query(l,r,R(i));
else return query(l,m,L(i))+query(m+1,r,R(i));
}
void update(int x,int i)//更新所有包含x这个数的区间val值都加1
{
tree[i].val++;
if(tree[i].l==tree[i].r){return;}//搜到头了
int m=(tree[i].l+tree[i].r)/2;
if(x<=m) update(x,L(i));//x<=m说明x是在结点i的左边,否则在右边
else update(x,R(i));
}
int main()
{
int n;
while(~scanf("%d",&n))
{
build(0,n-1,1);//建树
int sum=0;
for(int i=0;i<n;i++)
{
scanf("%d",&val[i]);
sum+=query(val[i],n-1,1);
update(val[i],1);//每次读进去一个数都要记录已经放进去的数中大于x的数的个数,然后记录下来
}
//到此为止记录的是按照读入顺序的一串数的逆序数
//按照题意,还可以把第一个数放到最后一个位置,这样求出一个最小的逆序数
//printf("sum=%d\n",sum);
int res=sum;
for(int i=0;i<n;i++)
{
sum+=n-val[i]-val[i]-1;//每一次把第一个数放到最后的位置上时,都要记录逆序数
res=min(res,sum);//res记录最小的逆序数
}
printf("%d\n",res);
}
return 0;
}