• hdu-1394(线段树求最小逆序数)


    http://acm.hdu.edu.cn/showproblem.php?pid=1394

    题意:

    给定一个n,然后又n个数字,首先,这些数字的大小是从0开始到n-1,比如样例n=10,则这十个数就是0,1,2,3,4,5,6,7,8,9,然后再将他们的顺序打乱来,构成一个数组.

    对于数组a,每次把最前面的下标的数放到末尾去,然后求n次操作中最小的逆序数的个数是多少.

    其中逆序数是当i<j时ai>aj.

    题解:

    首先由于数字给的很小,只有5000个,所以可以暴力.

    由于题目给的数字是从0到n-1.这个很重要,否则就要额外的加处理.

    先讲讲暴力的写法:

    首先对于一个数组a,我们有一个初始的逆序对的个数,这个可以直接两个for循环求出.

    然后重点来了,由于每次我们需要将数组的第一个数移动到末尾去,这个时候逆序对的个数就改变了.

    比如对于第一个数c,他后面会有t个逆序对数,由于数大小是固定的,也就是说他的逆序数就是数组中比他小的数的个数,所以这里逆序数个数 t1=c,这样一来剩下的数都是他的顺序数了,也就是说顺序数个数 t2=n-c-1.

    比如样例

    10 

    1 3 6 9 0 8 5 7 4 2

    这里逆序数有22个

    对于数字1来说,其逆序数只有1个,就是0,而顺序数就是后面的8个其他数字.

    然后再讲讲变化,试想一下,当我们把第一个数字放到最末尾去,那么对于整个数组的数来说,逆序对的个数就会减少 t1个,而同时又会增加 t2个.

    还是拿样例来说,当我们把1移动到了最后面去,这时

    10

    3 6 9 0 8 5 7 4 2 1

    这个时候逆序数就是 22-1+8=29个逆序数了.我们可以看到,由于移到了最后一个,当他在第一个位置时与他是逆序数的数都会变得反过来,同时与他是顺序对的数也会反过来变成逆序对.

    所以对于所有的数组,我们只要用一个for来循环一遍就可以求得每次的逆序数的总数,取其中最小的数就可以了.

    暴力AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    int dir[8][2]={{1,0},{0,1},{1,1},{1,-1},{-1,1},{-1,-1},{0,-1},{-1,0}};
    #define pi acos(-1)
    #define ls rt<<1
    #define rs rt<<1|1
    #define me0(s) memset(s,0,sizeof(s))
    #define me1(s) memset(s,1,sizeof(s))
    #define mef(s) memset(s,-1,sizeof(s))
    #define meinf(s) memset(s,inf,sizeof(s))
    #define llinf 1e18
    #define inf 1e9
    const int N=5700;
    int a[N];
    int main(int argc, char * argv[]){
           int n;
           while(~scanf("%d",&n)){
               int sum=0;
               for(int i=0;i<n;i++){
                   scanf("%d",&a[i]);
               }
               for(int i=0;i<n;i++){
                   for(int j=i+1;j<n;j++){
                       if(a[j]<a[i]){
                           sum++;
                       }
                   }
               }//求出最开始序列的逆序对的数目
               cout<<sum<<endl;
               int ans=sum;
               for(int i=0;i<n;i++){
                   sum=sum-(a[i]-n+1)-a[i];
                   if(ans>sum) ans=sum;
               }
               printf("%d
    ",ans);           
           }
        return 0;
    }

    以上是暴力的写法.

    然后有线段树的写法,线段树的写法完全就是取代第一步,求出初始的序列的逆序数的个数,因为暴力的话复杂度是n2的复杂度,数据稍微大点就会TLE了,所以要么用归并排序要么用线段树就可以很好的对其进行优化成nlogn的复杂度.

    叶子节点代表的左右端点a[i],节点中sum维护是否插入了a[i],按顺序插入a[i],每次插入查询区间[a[i],n-1]中的点数,因为这个区间内的点都比tree[i]先插入且比tree[i]大,所以这个区间内的点的个数就等于a[i]的逆序数。

    线段树AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    int dir[8][2]={{1,0},{0,1},{1,1},{1,-1},{-1,1},{-1,-1},{0,-1},{-1,0}};
    #define pi acos(-1)
    #define ls rt<<1
    #define rs rt<<1|1
    #define me0(s) memset(s,0,sizeof(s))
    #define me1(s) memset(s,1,sizeof(s))
    #define mef(s) memset(s,-1,sizeof(s))
    #define meinf(s) memset(s,inf,sizeof(s))
    #define llinf 1e18
    #define inf 1e9
    const int N=1e4+6;
    int sum[N<<2];
    void pushup(int rt){
        sum[rt]=sum[ls]+sum[rs];
    }
    void build(int l,int r,int rt){
        sum[rt]=0;
        if(l==r) return ;
        int m=(l+r)/2;
        build(l,m,ls);
        build(m+1,r,rs);
        pushup(rt);
    }
    void update(int l,int r,int p,int rt){
        if(l==r){
            sum[rt]++;
            return ;
        }
        int m=(l+r)/2;
        if(p<=m) update(l,m,p,ls);
        if(p>m) update(m+1,r,p,rs);
        pushup(rt);
    }
    int query(int L,int R,int l,int r,int rt){
        if(L<=l&&R>=r) return sum[rt];
        int ans=0;
        int m=(l+r)/2;
        if(L<=m) ans+=query(L,R,l,m,ls);
        if(R>m) ans+=query(L,R,m+1,r,rs);
        return ans;
    }
    int a[N];
    int main(int argc, char * argv[]){
           int n;
           while(~scanf("%d",&n)){
               build(0,n-1,1);
               int sum=0;
               for(int i=0;i<n;i++){
                   scanf("%d",&a[i]);
                   sum+=query(a[i],n-1,0,n-1,1);
                   update(0,n-1,a[i],1);
               }
               int ret=sum;
               for(int i=0;i<n;i++){
                   sum+=n-2*a[i]-1;
                   ret=min(ret,sum);
               }
               printf("%d
    ",ret);
           }
        return 0;
    }
  • 相关阅读:
    Java学习笔记
    计算机基础知识点整理
    codeblock的GUN GCC compiler问题
    秋招小米面经
    闭包的特性(只做了粗略整理)
    overflow:hidden为什么可以清除浮动?
    项目中出现的问题
    MySql 备忘还原数据库
    MySql语句备忘 JSON截取
    MySql语句备忘 UPDATE
  • 原文地址:https://www.cnblogs.com/wushengyang/p/11773896.html
Copyright © 2020-2023  润新知