HDU1394(最小逆序数)-线段树单点更新

一、题目

 
Problem Description 
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种排列(排列如下)的每种排列的逆序数求最大值:
a1, a2, ..., an-1, an
a2, a3, ..., an, a1
a3, a4, ..., an, a1, a2
…………. 
an, a1, a2, ..., an-1
当初做这一题,花了好多时间,百度了好多,也问了学长,初看下面的代码看不懂,几天后
再细看,终于弄懂了,同时自己写了个暴利的代码也过了,先说一下暴力的方法,再好过渡
到线段树的做法。

输入数组时,每输入一个数,就for(j=0;j<i-1;j++)比较大小,这样用sum把逆序数统计出来,其实这里才是暴力,至于后面的就很巧妙,公式很容易推出,因为题目总是把第一个数移到最后一个位置,所以原来比它小的数(和它构成逆序)在移动之后就不是逆序了,
而原来比它大的数(不和它构成逆序)在移动之后就是逆序了,这样sum就变化了:

结论:Sum=sum-(low[a[i]])+(up[a[i]]);
 

显然在序列0,1,2,…..n-1中,比a[i]小的数的个数是Low[a[i]]=a[i];  比a[i]大的数的个数是up[a[i]]=n-a[i]-1;
 
题目要求是循环移动n次,那么只要写个for,把a[0],a[1],a[2]……a[n-1]都移动一遍,sum进行n次上面的公式运算,同时记录最小值,就是最小逆序数了。

 
有了上面的说明,写暴力的代码就很简单了。

 
那么接下来就是过渡到线段树了。
 
发现上面分两部分,第一是统计初始的逆序数,第二是按顺序循环移动n个值,在第二部分是O(n)时间,无法再优化了。那么关键是就是第一部分的优化,刚开始我没看太懂别人的代码,后来才醒悟过来,这题说是用线段树做,其实仅是统计第一次的逆序数时用到,那
么线段树统计这第一次的逆序数思想是这样的:

 
先建一个空树;
 
逐个插入值(即输入的一个值);
 
在每插入一个值后就更新包含该区间的所有的数的个数(加一)。

为什么可以这样呢,下面引用一个人的说明。。

线段树求逆序数
  
3 2 5 4 6 1 
插入3 时 询问3-6 之间元素的个数v1=0;

插入2 时询问2-6之间元素的个数 v2=1
.                                                 v6=6
累加
v1
……
v6 =sum ;

再详细点,举个例子,插入2的时候,你要的找的数肯定是比2大的数的个数(当然是已经插进来了的,就是序列里排在2前面的),时候发现只有3比2先被插入到树中(且只有3而已),那么2由2产生的逆序数就是1了,累加到sum中;之后更新,update(2,1),把包含2的所有区间的数的个数都+1,便于之后的1,0,去查找。  这就是整个过程。。。。

#include<iostream>
#include<cstdio>

using namespace std;

#define N 5002

struct node
{
    int l,m,r;
    int num;
} tree[N*4];

int a[5001];

void creat(int id,int beg,int end)
{
    tree[id].l=beg;
    tree[id].r=end;
    tree[id].num=0;
    tree[id].m=(beg+end)>>1;
    if(beg<end)
    {
        creat(id*2,tree[id].l,tree[id].m);
        creat(id*2+1,tree[id].m+1,tree[id].r);
    }
}

void update(int id,int l,int r)
{
    if(tree[id].l==l&&tree[id].r==r)
    {
        tree[id].num+=1;
        return ;
    }
    else
    {
        if(tree[id].m>=l)
            update(id*2,l,r);          //在左子树
        else
            update(id*2+1,l,r);        //在右子树
    }
    tree[id].num=tree[id*2].num+tree[id*2+1].num;
}

int query(int id,int beg,int end)
{
    if(tree[id].l==beg&&tree[id].r==end)
    {
        return  tree[id].num;
    }
    else
    {
        if(tree[id].m>=end)
        {
            return query(id*2,  beg,end);
        }
        else if(tree[id].m<beg)
        {
            return query(id*2+1,beg,end);
        }
        else
        {
            return query(id*2,beg,tree[id].m)+find(id*2+1,tree[id].m+1,end);
        }
    }
}

int main()
{
    int n;
    while(cin>>n)
    {
        creat(1,1,n);
        int sum=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            a[i]++;
        }

        for(int i=1; i<=n; i++)
        {
            if(a[i]+1<=n)
                sum+=query(1,a[i]+1,n);
            update(1,a[i],a[i]);
//   cout<<sum<<endl;
        }
        int ans=sum;
        for(int i=1; i<=n-1; i++)
        {
            sum=sum+(n-a[i])-(a[i]-1);
            if(sum<ans) ans = sum;
        }
        cout<<ans<<endl;
    }
    return 0;
}
  


PS:此文章是网上搜罗然后各种PS所得。。。。终于搞完了线段树的单点更新。。明天开成段更新。。。另外如果neko13大哥看见的话,还请你多多推荐一些线段树单点更新的好题,学弟求练手啊~~~QAQ

 

 

你可能感兴趣的:(HDU1394(最小逆序数)-线段树单点更新)