题意:输入n,下面给出n个数,分别是0-n,顺序不定,可以通过对序列进行移动,每次移动把最前面的数移到后面,问通过移动形成的序列,使得得到的逆序数最少
思路:这题的特殊性方便了我们去寻找最小值。现在假设我们知道了原序列的逆序数是first,那么每次移动后怎么算出新的逆序数呢?因为每次都只是移动头元素,假设头元素为x,那么可以知道由x产生的逆序对的个数为x,因为有x个数小于它(0,1,2……x-1),如果将它放到了末尾,那么这x个逆序对将会消失。同样地,整个序列中有(n-1-x)个元素大于x,那么x移到了末尾,将产生(n-1-x)个新的逆序对(这些逆序对分别为(x+1,x),(x+2,x),(x+3,x)……(n-1,x))。因此可以递推地解决这个问题。如果知道了当前序列逆序数为sum,那么移动头元素后的逆序数将会是sum-x+(n-1-x)
那么接下来的问题就是求原序列的逆序数,得到它就可以知道所有其他序列的逆序数。用线段数解决
看列子:(某大神的解释)
先以区间[0,9]为根节点建立val都为0的线段树,
再看看怎样求下面序列的逆序数:
1 3 6 9 0 8 5 7 4 2
在线段树中插入1, 插入之前先询问区间[1,9]已插入的节点数(如果存在,必与1构成逆序) v1=0
在线段树中插入3, 插入之前先询问区间[3,9]已插入的节点数(如果存在,必与3构成逆序) v2=0
在线段树中插入6, 插入之前先询问区间[6,9]已插入的节点数(如果存在,必与6构成逆序) v3=0
在线段树中插入9, 插入之前先询问区间[9,9]已插入的节点数(如果存在,必与9构成逆序) v4=0
在线段树中插入0, 插入之前先询问区间[0,9]已插入的节点数(如果存在,必与0构成逆序) v5=4
在线段树中插入8, 插入之前先询问区间[8,9]已插入的节点数(如果存在,必与8构成逆序) v6=1
在线段树中插入5, 插入之前先询问区间[5,9]已插入的节点数(如果存在,必与5构成逆序) v7=3
在线段树中插入7, 插入之前先询问区间[7,9]已插入的节点数(如果存在,必与7构成逆序) v8=2
在线段树中插入4, 插入之前先询问区间[4,9]已插入的节点数(如果存在,必与4构成逆序) v9=5
在线段树中插入2, 插入之前先询问区间[2,9]已插入的节点数(如果存在,必与2构成逆序) v10=7
累加v1+……+v10 =22,这就是1 3 6 9 0 8 5 7 4 2的逆序数了.
其实就是统计一个区间内已经插入了多少个元素,也可以说成是区间求和(一个区间能达到的最大和就是该区间长度,现在的和就是已经插入在内的元素个数)
代码:
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <stack>
#define lson l,mid,num<<1
#define rson mid+1,r,num<<1|1
using namespace std;
const int M = 5005;
int sum[M<<2];
void PushUp(int num)
{
sum[num]=sum[num<<1]+sum[num<<1|1];
}
void build(int l,int r,int num)
{
if(l==r)return;
int mid=(l+r)>>1;
build(lson);
build(rson);
}
void update(int p,int l,int r,int num)
{
if(l==r)
{
sum[num]++;
return;
}
int mid=(l+r)>>1;
if(p<=mid)update(p,lson);
if(p>mid)update(p,rson);
PushUp(num);
}
int query(int L,int R,int l,int r,int num)
{
if(L<=l && r<=R)
return sum[num];
int mid=(l+r)>>1;
int ans=0;
if(L<=mid)ans+=query(L,R,lson);
if(R>mid)ans+=query(L,R,rson);
return ans;
}
int main()
{
int n,x[M];
while(scanf("%d",&n)==1)
{
memset(sum,0,sizeof(sum));
int ans=0;
build(0,n-1,1);
for(int i=1; i<=n; i++)
{
scanf("%d",&x[i]);
ans+=query(x[i],n-1,0,n-1,1);
update(x[i],0,n-1,1);
}
int mmin=ans;
for(int i=1;i<=n;i++)
{
ans=ans-x[i]+(n-x[i]-1);
mmin=min(mmin,ans);
}
printf("%d\n",mmin);
}
return 0;
}