• hdu 1394 Minimum Inversion Number


    线段树 or 树状数组 or 归并排序

    一个关于逆序数的问题。输入n,下面给出n个数字,分别是0到n-1,顺序不定。问你逆序数是多少?

    逆序数就是逆序对的个数,所谓逆序对,就是一个序列中任取两个数字,若i<j,a[i]>a[j],则称a[i]和a[j]是一个逆序对,好像3,1,4,2,逆序对有(3,1),(3,2),(4,2),所以逆序数为3

    但题目还没完,它还需要移动,每次将序列的第一个元素放在序列的最后,得到的新序列再求一次逆序数,可知一个长度为n的序列这样的移动可以做n-1次,可以得到n-1个新的序列,(第n次的话就会变回原始的序列)。然后问你,原序列和n-1个新序列中最小的逆序数是多少,然后输出。

    1.这题的特殊性方便了我们去寻找最小值。现在假设我们知道了原序列的逆序数是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)

    2.那么接下来的问题就是怎么求原序列的逆序数,得到它就可以知道所有其他序列的逆序数。用线段数解决

    先以区间[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 <cstdio>
    #include <cstring>
    //#define max(a,b) a>b?a:b
    #define N 5000
    
    struct node
    {
        int l,r,c;
    }t[4*N+10];
    int x[N],n;
    
    void build(int a ,int b ,int rt)
    {
        t[rt].l=a; t[rt].r=b; t[rt].c=0;
        if(a==b) return ;
        int mid=(a+b)>>1;
        build(a,mid,rt<<1);
        build(mid+1,b,rt<<1|1);
    }
    
    int query(int a, int b ,int rt)
    {
        if(t[rt].l==a && t[rt].r==b) return t[rt].c;
        int mid=(t[rt].l+t[rt].r)>>1;
        if(a>mid)       return query(a, b, rt<<1|1); //只访问右子树
        else if(b<=mid) return query(a, b, rt<<1); //只访问左子树
        else            return query(a, mid, rt<<1)+query(mid+1, b, rt<<1|1);; //左右均访问
    }
    
    void updata(int val , int rt)
    {
        t[rt].c++;
        if(t[rt].l==t[rt].r) return ;
        int mid=(t[rt].l+t[rt].r)>>1;
        if(val>mid) updata(val, rt<<1|1);
        else        updata(val, rt<<1);
    }
    
    int min(int a ,int b)
    { return a<b?a:b; }
    
    int main()
    {
        while(scanf("%d",&n)!=EOF)
        {
            build(0,n-1,1);
            int first=0;
            for(int i=1; i<=n; i++)
            {
                scanf("%d",&x[i]);
                first+=query(x[i],n-1,1);
                updata(x[i],1);
            }
    //        printf("first=%d\n",first);
            int ans,sum;
            ans=sum=first;
            for(int i=1; i<=n-1; i++)
            {
                sum=sum-x[i]+(n-1-x[i]);
                ans=min(ans,sum);
            }
            printf("%d\n",ans);
        }
        return 0;
    }

     用树状数组解决,和线段树是完全一样的思想,就是统计,一开始c数组先清空为0,没读入一个数字,相当于相对应的a[i]增1,所以将增1沿路径修改c数组的值。

    对于每个读入的元素x,就看[x+1,n-1]已经读入了多少个,就是这个数字产生的逆序对

    #include <cstdio>
    #include <cstring>
    #define N 5010
    
    int lowbit(int n)
    {  return n&(-n);  }
    
    void add(int *c ,int p ,int n,int k)
    {
        while(p <= n)
        {
            c[p] += k;
            p += lowbit(p);
        }
    }
    
    int sum(int *c ,int p)
    {
        int ans=0;
        while(p)
        {
            ans += c[p];
            p -= lowbit(p);
        }
        return ans;
    }
    
    int main()
    {
        int n,m,first;
        int c[N],a[N];
        while(scanf("%d",&n)!=EOF)
        {
            memset(c,0,sizeof(c));
            first=0;
            for(int i=0; i<n; i++)
            {
                scanf("%d",&m);
                a[i]=m;
                //接着找出[m+1,n-1]里面已经有多少个元素
                //其实就是c[n-1]-c[m]
                //但c数组是从下标1开始的,所以c[n]-c[m+1]
                int s1,s2;
                add(c,m+1,n,1);  //m+1这个位置(下标从1开始)的元素加1
                s1=sum(c,m+1);
                s2=sum(c,n);
                first += (s2-s1);
            }
            //printf("%d\n",first);
            int ans=first , pre=first;
            for(int i=0; i<n-1; i++)
            {
                int x=a[i],s;
                s = pre-x+n-1-x;  //少了x对,多了n-1-x对
                if(s<ans) ans=s;
                pre=s;
            }
            printf("%d\n",ans);
        }
        return 0;
    }

    用归并排序来求解逆序对问题

    在合并的过程中是将两个相邻并且有序的序列合并成一个有序序列,如以下两个有序序列

    Seq13  4  5

    Seq22  6  8  9

    合并成一个有序序:

    Seq2  3  4  5  6  8  9

    对于序列seq1中的某个数a[i],序列seq2中的某个数a[j],如果a[i]<a[j],没有逆序数,如果a[i]>a[j],那么逆序数为seq1a[i]后边元素的个数(包括a[i]),即len1-i+1,

    这样累加每次递归过程的逆序数,在完成整个递归过程之后,最后的累加和就是逆序的总数

    #include <cstdio>
    #include <cstring>
    #define min(a,b) a<b?a:b
    #define N 5050
    
    int first,n;
    
    void merge(int *c ,int *b , int left ,int right)
    {
        int mid=(left+right)>>1 , i=left , j=mid+1 , k=left;
        while(i<=mid && j<=right)
        {
            if(c[i]>c[j]) //前部分大,要计数
            {
                b[k++]=c[j++];
                first+=(mid-i+1);
            }
            else b[k++]=c[i++];
        }
    
        if(i<=mid)
            while(i<=mid) { /*first+=(mid-i+1);*/ b[k++]=c[i++];  }
        else
            while(j<=right)  b[k++]=c[j++]; 
    }
    
    void mergesort(int *a , int *b , int left , int right)
    {//把a数组的元素排好序保存在b数组中
        if(left==right) { b[left]=a[left]; return ;}
        int mid=(left+right)>>1 , c[N];
        mergesort(a,c,left,mid); //将a数组前部分排好临时保存在c数组中
        mergesort(a,c,mid+1,right); //将a数组后部分排好临时保存在c数组中
        merge(c,b,left,right); //将前后部分都有序的c数组合并在b数组中
    }
    
    int main()
    {
        int a[N],x[N];
        while(scanf("%d",&n)!=EOF)
        {
            for(int i=1; i<=n; i++) scanf("%d",&x[i]);
            first=0;
            mergesort(x,a,1,n); //将x数组的元素排好序保存在a数组中,要保存x数组不变
    //        printf("%d\n",first);
            int ans,sum;
            ans=sum=first;
            for(int i=1; i<=n-1; i++)
            {
                sum=sum-x[i]+(n-1-x[i]);
                ans=min(ans,sum);
            }
            printf("%d\n",ans);
        }
        return 0;
    }
  • 相关阅读:
    compareTo冒泡比较时间字符串
    RestTemplate
    poi 处理空单元格
    Linux执行Java文件
    cmd 运行 java 文件
    @RequestParam 引发的编译问题
    linux 下安装与使用
    ajax请求 Provisional headers are show
    JWT加密
    Web API Filter
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2922874.html
Copyright © 2020-2023  润新知