hdu 1394 Minimum Inversion Number (树状数组)

来源:互联网 发布:uu淘宝店没评价 编辑:程序博客网 时间:2024/05/16 01:08

题意:求给出的一串数字序列的各种排列的最小逆序对数。这里的各种排列是将第一个元素移至最后形成的新排列。


求逆序对数很自然的想到了树状数组,又快又简便。


最开始的想法是,我们能用树状数组快速的求出某一串序列的逆序对数,但是这里对于一串n长的数列它的各种排列就有n种,如果对每一种排列都用树状数组求一次逆序对数,那么时间复杂度将达到O(n^2logn),这对于题目给的数量级别及时间限制,是无法满足此题的。

那么转过来想一下,我们是否可以快速的求出它的各种排列的逆序对数呢?

根据题目的意思,它所说的各种排列是将第一个元素移至最后形成的排列,那么我们就从这里下手,

对于第一个元素它后面比它小的就一定都会形成逆序对,这样对于当前的逆序对,在第一个元素移至最后时,它的逆序对数就要减少这个元素的值再减去一,因为此题数值是连续的所以可以直接减,而在移至最后时,大于这个元素的数值的数和它都会形成逆序对。这样我们在减了之前的值之后还要加上总的元素的个数减去这个元素的值,这样得到的一个值就是新排列的逆序对数了。例:我们要将a[0]移至末尾,总元素的个数是n,当前的逆序对数是ans,那么将a[0]移至末尾时,ans = ans - a[0] - 1 +  n - a[0] 

有了这个方法,那么我们就可以在O(n)的时间内算出所有排列的最小逆序对数了。总的时间复杂度是O(nlogn),O(n)是读入元素的复杂度,对每个元素进行处理是O(logn)的复杂度。


下面贴上代码:

#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define N 5001#define ll long longll C[N];ll num[N];int T;int Lowbit(int x){    return x&(x^(x-1));}void add(ll C[],ll pos,ll num) {    while(pos <= N) {//x最大是N        C[pos] += num;        pos += Lowbit(pos);    }}ll Sum(ll C[],ll end) {    ll sum = 0;    while(end > 0) {        sum += C[end];        end -= Lowbit(end);    }    return sum;}int main() {    int s, t, i, j, T, k;    ll ans, tmp;    while(~scanf("%d",&T)) {        memset(C,0,sizeof(C));        memset(num,0,sizeof(num));        ans = 0;        for(i = 0; i < T; i ++) {            scanf("%I64d",&num[i]);            add(C,num[i]+1,1);            ans += i + 1 - Sum(C,num[i]+1);//求出前面比它小的,然后用i减去之,得到的就是前面比它大的数的个数            /*            ans += Sum(C,T) - Sum(C,num[i]+1);            add(C,num[i]+1,1);            //这种方法也是求出逆序数的个数,            //原理刚好和上面相反,即求出你后面现在有多少个数是大于你的,            //因为还没将此时的数加进树状数组,所以树状数组里的数都是之前的,也就都是它前面的数,            //利用Sum函数的性质,Sum求出的是前面比它小的数的个数,用Sum(T)减去之就得到了前面比它大的数的个数了。            */        }        tmp = ans;        for(i = 0; i < T; ++i){            ans += T - 2 * num[i] - 1;//直接得到            tmp = ans > tmp?tmp:ans;        }        printf("%I64d\n",tmp);    }    return 0;}