The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.
For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:
a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)
You are asked to write a program to find the minimum inversion number out of the above sequences.
Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.
Output
For each case, output the minimum inversion number on a single line.
Sample Input
10
1 3 6 9 0 8 5 7 4 2
Sample Output
16
题意:
给出n表示有0~n-1共n个数
然后给出初始序列
现在我们把初始序列第一位数放在最后一位这样就构成一个新的序列,然后在新的序列上继续执行上述操作,不断把第一位数放在最后一位,每个序列都有一个逆序数,求这n个序列的最小逆序数。
解题思路:
可能是昨晚没睡好今天有点迷糊,看了很多讲线段树求逆序数的文章都没有很理解,
于是顺着别人的思路和代码自己推了推,用自己的理解再总结一下思路.
这题可以使用暴力、归并排序、线段树/树状数组等方法来做,这里只介绍线段树的做法。
逆序数:如果i<j但a[i]>a[j] 则nx[i]++(i的逆序数)
分析数列第一个数i,设比它大的数字有b[i]个,
也就是说和这个数字组成了b[i]个逆序,
把它放到最后,这个数字可以组成 n-1-b[i]个逆序(1是它本身),
所以逆序数的增量是 n-1-b[i]-b[i],这样就可以根据原来的序列的逆序数求出剩下的所有序列的逆序数了~
这里的方法是:
先求出初始序列的逆序数ans,然后每生成一个新序列就求一次当前序列的逆序数,有了上面的分析,我们就无需执行生成新的逆序数的操作,只需遍历每个数在初始序列逆序数ans进行加减即可。
如我们初始序列逆序数是ans,则下一个序列的逆序数就是ans+(n-1-b[0]-b[0])
再下一个序列的逆序数就是ans+(n-1-b[1]-b[1])......
线段树求逆序数的思路:
初始树中的值都为0
每插入一个数(在树中找到下标a[i]的结点),更新这个结点的值为1,并更新这个树.
每插入一个数后就计算当前已插入序列的逆序数是多少。
如我们插入a[i],
如果在[a[i]+1,n]区间(下标从1开始,方便理解), 找到比a[i]大的数(记为bigger),
说明bigger比a[i]先插入树中即bigger在序列中是在a[i]的前面的,
因为每插入一个值,该结点的值就更新为1,
所以统计这个区间里所有的bigger,即是sum加上这个区间的和(sum初始为0),
1就表示有1个bigger,2就表示有两个bigger...
如本题 1 3 6 9 0 8 5 7 4 2
用线段树求这个初始序列的逆序数 每次询问[a[i]+1,n-1](下标从0开始,最大的数是n-1)
插入1 询问[2-9]区间有没有已经插入比1大的数 v1=0
插入3 询问[4-9]区间有没有已经插入比3大的数 v2=0
插入6 询问[7-9]区间有没有已经插入比6大的数 v3=0
插入9 [10-9]区间 不找
插入0 询问[1-9]区间有没有已经插入比0大的数 v4=4
插入8 询问[9-9]区间有没有已经插入比8大的数 v5=1
插入5 询问[6-9]区间有没有已经插入比5大的数 v6=3
插入7 询问[8-9]区间有没有已经插入比7大的数 v7=2
插入4 询问[5-9]区间有没有已经插入比4大的数 v8=5
插入2 询问[3-9]区间有没有已经插入比2大的数 v9=7
所以该初始序列的逆序数是ans=v1+v2+...+v9=22
知道初始序列的逆序数然后按照上面的思想,从当前序列的第一位到最后一位遍历,
当把第一位放在最后一位时,逆序数ans+=n-1-a[i]-a[i](a[i]表示当前遍历到这个数,这个数是这次序列的第一个数),
遍历所有数即是遍历所有序列的情况,得出最小的逆序数minn
总结以上,我们用到了线段树的单点更新和区间查询操作.
如果有不准确的地方欢迎评论指出
AC代码(含模板):
#include<bits/stdc++.h>
using namespace std;
#define N 5010
int a[N];
struct node
{
int l,r,w,lazy;//tree的l,r表示数组区间[l,r],w表示[l,r]区间和
}tree[4*N];
//lazy!=0是加值,lazy!=-1是改值
void build(int v,int l,int r)//建树,v表示tree里第v个结点,tree是完全二叉树
{
tree[v].l=l;
tree[v].r=r;
if(tree[v].l==tree[v].r)
{
tree[v].w=0;
return;
}
int mid=(l+r)/2;
build(v*2,l,mid);
build(v*2+1,mid+1,r);
tree[v].w=tree[v*2].w+tree[v*2+1].w;
}
//void downadd(int v)//区间加值lazy=0 标记下传
//{
// tree[v*2].lazy+=tree[v].lazy;
// tree[v*2+1].lazy+=tree[v].lazy;
// tree[v*2].w+=tree[v].lazy*(tree[v*2].r-tree[v*2].l+1);
// tree[v*2+1].w+=tree[v].lazy*(tree[v*2+1].r-tree[v*2+1].l+1);
// tree[v].lazy=0;
//}
//void downupdate(int v)//区间改值lazy=-1 标记下传
//{
// tree[v*2].lazy=tree[v].lazy;
// tree[v*2+1].lazy=tree[v].lazy;
// tree[v*2].w=tree[v].lazy*(tree[v*2].r-tree[v*2].l+1);
// tree[v*2+1].w=tree[v].lazy*(tree[v*2+1].r-tree[v*2+1].l+1);
// tree[v].lazy=-1;
//}
//int ask_point(int v,int x)//单点查询
//{
// if(tree[v].l==tree[v].r)
// {
// return tree[v].w;
// }
//
// if(tree[v].lazy!=0) downadd(v);
// //if(tree[v].lazy!=-1) downupdate(v);//区间改值用-1
//
// int mid=(tree[v].l+tree[v].r)/2;
// if(x<=mid) ask_point(v*2,x);
// else ask_point(v*2+1,x);
//}
void change_point(int v,int x,int y)//单点修改,a[x]改为y(或加减等操作)
{
if(tree[v].l==tree[v].r)
{
//tree[k].w+=y;
tree[v].w=y; //找到了x这个点,a[x]=y,也可进行其他操作
return;
}
//if(tree[v].lazy!=0) downadd(v);
//if(tree[v].lazy!=-1) downupdate(v);//区间改值用-1
int mid=(tree[v].l+tree[v].r)/2;
if(x<=mid) change_point(v*2,x,y);
else change_point(v*2+1,x,y);
tree[v].w=tree[v*2].w+tree[v*2+1].w;
}
int ask_interval(int v,int a,int b)//区间查询[a,b]
{
if(tree[v].l>=a&&tree[v].r<=b)
{
return tree[v].w;
}
//if(tree[v].lazy!=0) downadd(v);
//if(tree[v].lazy!=-1) downupdate(v);//区间改值用-1
int sum=0;
int mid=(tree[v].l+tree[v].r)/2;
if(a<=mid) sum+=ask_interval(v*2,a,b);
if(b>mid) sum+=ask_interval(v*2+1,a,b);
return sum;
}
//void changeadd_interval(int v,int a,int b,int y)//区间加值,[a,b]内所有数同时+y
//{
// if(tree[v].l>=a&&tree[v].r<=b)
// {
// tree[v].w+=(tree[v].r-tree[v].l+1)*y;
// tree[v].lazy+=y;
// return;
// }
// if(tree[v].lazy!=0) downadd(v);
// //if(tree[v].lazy!=-1) downupdate(v);//区间改值用-1
//
// int mid=(tree[v].l+tree[v].r)/2;
// if(a<=mid) changeadd_interval(v*2,a,b,y);
// if(b>mid) changeadd_interval(v*2+1,a,b,y);
//
// tree[v].w=tree[v*2].w+tree[v*2+1].w;
//}
//void changeupdate_interval(int v,int a,int b,int y)//区间改值,[a,b]内所有数同时修改为y
//{
// if(tree[v].l>=a&&tree[v].r<=b)
// {
// tree[v].w=(tree[v].r-tree[v].l+1)*y;
// tree[v].lazy=y;
// return;
// }
// if(tree[v].lazy!=0) downadd(v);
// //if(tree[v].lazy!=-1) downupdate(v);//区间改值用-1
//
// int mid=(tree[v].l+tree[v].r)/2;
// if(a<=mid) changeupdate_interval(v*2,a,b,y);
// if(b>mid) changeupdate_interval(v*2+1,a,b,y);
//
// tree[v].w=tree[v*2].w+tree[v*2+1].w;
//}
int main()
{
int n;
while(~scanf("%d",&n))
{
build(1,0,n-1);
int ans=0;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
ans+=ask_interval(1,a[i]+1,n-1);
change_point(1,a[i],1);
}
int minn=ans;
for(int i=0;i<n;i++)
{
ans+=n-2*a[i]-1;
if(ans<minn) minn=ans;
}
printf("%d\n",minn);
}
return 0;
}
去注释:
#include<bits/stdc++.h>
using namespace std;
#define N 5010
int a[N];
struct node
{
int l,r,w,lazy;
}tree[4*N];
void build(int v,int l,int r)
{
tree[v].l=l;
tree[v].r=r;
if(tree[v].l==tree[v].r)
{
tree[v].w=0;
return;
}
int mid=(l+r)/2;
build(v*2,l,mid);
build(v*2+1,mid+1,r);
tree[v].w=tree[v*2].w+tree[v*2+1].w;
}
void change_point(int v,int x,int y)
{
if(tree[v].l==tree[v].r)
{
tree[v].w=y;
return;
}
int mid=(tree[v].l+tree[v].r)/2;
if(x<=mid) change_point(v*2,x,y);
else change_point(v*2+1,x,y);
tree[v].w=tree[v*2].w+tree[v*2+1].w;
}
int ask_interval(int v,int a,int b)
{
if(tree[v].l>=a&&tree[v].r<=b)
{
return tree[v].w;
}
int sum=0;
int mid=(tree[v].l+tree[v].r)/2;
if(a<=mid) sum+=ask_interval(v*2,a,b);
if(b>mid) sum+=ask_interval(v*2+1,a,b);
return sum;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
build(1,0,n-1);
int ans=0;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
ans+=ask_interval(1,a[i]+1,n-1);
change_point(1,a[i],1);
}
int minn=ans;
for(int i=0;i<n;i++)
{
ans+=n-2*a[i]-1;
if(ans<minn) minn=ans;
}
printf("%d\n",minn);
}
return 0;
}