题目:点击打开题目链接
题意:输入一个初始序列,然后每次操作都把序列的第一个数放到最后一个位置,构成一个新的序列,问其中某一个序列的最小逆序数是多少。
思路:首先,普及一下逆序与逆序数的概念,简单地说就是如果前面的数比后面的数大,就称为一个逆序。一个排列中逆序的总数称为这个排列的逆序数。
然后这道题有一个好处就是这n个数是由0~n-1组成的,那么,这意味着什么呢?仔细观察后,我们不难发现
假设0~n-1中的一个数x放到了最后一个位置,那么这个序列的逆序数就是n-1-x,这是为什么呢,下面我们接着看
因为在这个序列中,比x小的数有x个(始终记住这n个数是由0~n-1组成的),比x大的数有n-1-x个,那么,将x放到最后一个位置,也就是说,这个序列中的前n-1个数(去掉最后一个数)中有x个比它小的数,有n-1-x个比它大的数,所以这个序列的逆序数就是n-1-x。
我们假设初始序列的逆序数是ans,那么把序列中的x放到最后一个位置之后,逆序数就变成了ans - x +(n-1-x),减x是因为原本x是第一个数,后面有x个比它小的数,现在把它放到了最后一个位置,那么序列中前面有x个对于x来说是有序的,只有那n-1-x个比它大的数才是逆序,所以逆序数要-x。
所以问题就转化成了求初始序列的逆序数,用线段树的方法单点更新,区间求和
主要思想就是把每个数放到自己的叶子结点,然后看它前面有多少个比它大的数,最后累加就是序列的逆序数。
My DaiMa:
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
#define lson l,mid,num<<1
#define rson mid+1,r,num<<1|1
const int maxn = 5005;
int sum[maxn<<2];
void push_up(int num)
{
sum[num] = sum[num<<1] + sum[num<<1|1];
}
/*建树*/
void TreeBuild(int l,int r,int num)
{
sum[num] = 0;//初始都是0
if(l == r) return;
int mid = (l+r) >> 1;
TreeBuild(lson);
TreeBuild(rson);
push_up(num);
}
/*每插入一个数,更新*/
void update(int x,int l,int r,int num)
{
if(l == r)
{
sum[num] ++;
return;
}
int mid = (l+r) >> 1;
if(x > mid) update(x,rson);
else update(x,lson);
push_up(num);
}
/*查询,判断前面有多少个比它大的数*/
int query(int x,int y,int l,int r,int num)
{
if(x <= l && y >= r) return sum[num];
int ans = 0;
int mid = (l+r) >> 1;
if(x <= mid) ans += query(x,y,lson);
if(y > mid) ans +=query(x,y,rson);
return ans;
}
int main()
{
int n,a[5005];
while(cin >> n)
{
TreeBuild(0,n-1,1);
int ans = 0;
for(int i = 0; i < n; i++)
{
scanf("%d",&a[i]);
ans += query(a[i],n-1,0,n-1,1);//先查询
update(a[i],0,n-1,1);//后更新
}
int minn = ans;
for(int i = 0; i < n; i++)
{
ans = ans - a[i] + (n - 1 - a[i]);
minn = min(minn,ans);
}
cout << minn << endl;
}
}