hdu1394 Minimum Inversion Number 循环串的最小逆序数 暴力 归并 线段树 树状数组

版权声明:点个关注(^-^)V https://blog.csdn.net/weixin_41793113/article/details/89225277

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394

问题描述

给定数字序列a1,a2,...,an的反转数是满足i <j和ai> aj的对(ai,aj)的数量。

对于给定的数字序列a1,a2,...,a,如果我们将第一个m> = 0个数字移动到seqence的末尾,我们将获得另一个序列。总共n个如下序列:

a1,a2,...,an-1,an(其中m = 0 - 初始序列)
a2,a3,...,an,a1(其中m = 1)
a3,a4,...,an,a1,a2(其中m = 2)
... 
an,a1,a2,...,an-1(其中m = n-1)

您被要求编写程序从上述序列中找出最小反转数。

输入

输入包含许多测试用例。每个案例由两行组成:第一行包含正整数n(n <= 5000); 下一行包含从0到n-1的n个整数的排列。

产量

对于每种情况,在一条线上输出最小反转数。

样本输入

10

1 3 6 9 0 8 5 7 4 2

样本输出

16

 

解法1:暴力+数学优化

找出规律,每次将最前面的数移至末尾,大于arr[i]的数为(N-1-arr[i]),小于arr[i]的数为arr[i].

大佬们暴力找逆序对的时候都是点向后找,这里确定是向后找,才容易联想到上面的这个规律

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;


int a[5005];

int main(){
    int n,ans,cnt;

    while(~scanf("%d",&n)){
        ans=99999999;
        cnt=0;
        memset(a,0,sizeof(a));

        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            for(int j=1;j<i;j++)
                if(a[j]>a[i])
                    cnt++;
        }
        ans = min(ans,cnt);

        for(int i=1;i<=n;i++){
            cnt = cnt - a[i] + n - a[i] - 1;
            ans = min(ans,cnt);
        }

        printf("%d\n",ans);
    }


	return 0;
}

解法2:归并排序

最后的处理循环操作不能对a数组操作,因为它排好序了

#include<iostream>
#include<cstdio>
using namespace std;

int a[5005],b[5005],c[5005];
int n;


int merge(int l,int r) {
    int mid = l + (r-l)/2;
    int i=l,j=mid+1,k=l,ans=0;
    for(int i=l; i<=r; i++)
        b[i] = a[i];

    while(i<=mid && j<=r) {
        if(b[i]>b[j]) {
            a[k++] = b[j++];
            ans+=mid+1-i;
        } else
            a[k++] = b[i++];
    }

    while(i<=mid) {
        a[k] = b[i];
        k++,i++;
    }
    while(j<=r) {
        a[k] = b[j];
        k++,j++;
    }

    return ans;
}

int merge_sort(int l,int r) {
    if(l>=r)
        return 0;

    int ans = 0;
    int mid = l + (r-l)/2;
    ans+=merge_sort(l,mid);
    ans+=merge_sort(mid+1,r);
    ans+=merge(l,r);

    return ans;
}


int main() {

    while(~scanf("%d",&n)) {
        for(int i=1; i<=n; i++){
            scanf("%d",&a[i]);
            c[i] = a[i];
        }

        int cnt = merge_sort(1,n);
        int ans = cnt;
        for(int i=1;i<=n;i++){
            cnt = cnt - c[i] + n - c[i] - 1;//不能对a数组操作,因为它排好序了
            ans = min(ans,cnt);
        }

        printf("%d\n",ans);
    }




    return 0;
}

解法3:线段树

从左开始插入a[]数组,用线段树表示区间[l,r]中已经插入的数字总数,每次插入a[i]前,先查找区间[a[i],n-1]中的已插入的总数,这就是a[i]所构成的逆序对,然后再把a[i]插入到相应的下标

#include<iostream>
#include<cstdio>
using namespace std;

const int MAX = 5050;

struct {
    int l,r,sum;
}tree[4*MAX];

int n;
int a[MAX];

void buildTree(int u,int l,int r){
    tree[u].l = l;
    tree[u].r = r;
    tree[u].sum = 0;
    if(l==r)
        return;
    int mid = l + (r-l)/2;
    buildTree(2*u,l,mid);
    buildTree(2*u+1,mid+1,r);
}

int query(int u,int x,int y){
    if(x<=tree[u].l && y>=tree[u].r)
        return tree[u].sum;
    int mid = tree[u].l + (tree[u].r-tree[u].l)/2;
    if(y<=mid)
        return query(2*u,x,y);
    else if(x>mid)
        return query(2*u+1,x,y);
    else
        return query(2*u,x,mid)+query(2*u+1,mid+1,y);
}

void update(int u,int x){
    if(tree[u].l==x && tree[u].r==x){
        tree[u].sum=1;
        return;
    }
    int mid = tree[u].l + (tree[u].r-tree[u].l)/2;
    if(x<=mid)
        update(2*u,x);
    else if(x>mid)
        update(2*u+1,x);
    tree[u].sum = tree[2*u].sum + tree[2*u+1].sum;
}


int main(){

    while(~scanf("%d",&n)){
        buildTree(1,0,n-1);//tree[]表示0~n-1区间有多少数
        int cnt = 0;
        for(int i=0;i<n;i++){//a数组下标从1或者0开始都可以,看你喜欢
            scanf("%d",&a[i]);
            cnt+=query(1,a[i],n-1);
            update(1,a[i]);
        }

        int ans = cnt;
        for(int i=0;i<n;i++){
            cnt = cnt - a[i] + n - a[i] - 1;
            ans = min(ans,cnt);
        }

        printf("%d\n",ans);
    }

	return 0;
}

解法4:树状数组

 

猜你喜欢

转载自blog.csdn.net/weixin_41793113/article/details/89225277