hdu 6318 Swaps and Inversions多校--补题 树状数组+离散化求逆序数

题意:给你一列数,每对逆序数都要给x元,但是在给逆序数的钱前之前,你可以多次交换相邻的两个数,每次交换给y元,问最少要付多少钱。

条件:1 逆序数: 1 ≤ i < j ≤ n 而且 A[i] > A[j](可以相邻也可以不相邻)

思路:对一个无序序列进行排序,要求一次只能交换相邻的两个数,最少需要交换次数就是逆序数的对数,所以只需要求出逆序数乘以min(x,y)。一般求逆序数是有三种方法的。第三种方法才是ac的。

第一种方法:归并排序求逆序数,但是在这道题里会超时,但还是学一下。归并排序其实就是计算每个小区间的逆序数,进而得到大区间的逆序数。(归并排序 本题超时

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e5+5;
long long int count1;
int num[maxn],n,y,x;
void meger(int fst,int mid,int lst)
{
    int i=fst,j=mid+1,k=fst;
    int b[n];
    while(i<=mid&&j<=lst)
    {
        if(num[i]<=num[j])
        {
            b[k++]=num[i++];
        }
        else
        {
            b[k++]=num[j++];
            count1+=mid-i+1;//计算逆序数
        }
    }
    while(i<=mid)
        b[k++]=num[i++];
    while(j<=lst)
        b[k++]=num[j++];
    for(int h=fst;h<=lst;h++)
    {
        num[h]=b[h];
    }
}
void merge_sort()//这里采用了非递归写法,原先以为超时是因为递归,nlogn多case
{
    for(int i=1;i<n;i*=2)
    {
        int t=0;
        while(t+i<n)
        {
            int mid=t+i-1;
            int lst=mid+i;
            if(lst>n-1)lst=n-1;
            meger(t,mid,lst);
            t=lst+1;
        }
    }
    return ;
}
int main()
{
    while(~scanf("%d%d%d",&n,&x,&y))
    {
        for(int i=0;i<n;i++)
            scanf("%d",&num[i]);
        count1=0;//记录逆序数
        merge_sort();//归并排序
        if(y<x)
            printf("%lld\n",y*count1);
        else
            printf("%lld\n",x*count1);
    }
    return 0;
}

第二种方法:树状数组,适用于数据范围小的,这题数据范围10的9次方,数组最多开到500000。开一个存数据的数组,每次输入的数据都是该数组的下标,然后树状数组每次求出在它前面比它大的个数,就是该数对应的逆序数,然后全部相加就是总的逆序数。(树状数组 本题超空间

举例:数列 5 8 3 1

数列每次加入数字后的变化 每次加入的数前面比它大的数的个数
{} 0
{5} 0
{5 8} 0
{5 8 3} 2
{5 8 3 1} 3

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e5+5;
int c[maxn];
int n,x,y;
int lowbit(int x)
{
    return x&(-x);
}
int insert_num(int i,int x)
{
    while(i<=n)
    {
        c[i]+=x;
        i+=lowbit(i);
    }
    return 0;
}
int getsum(int i)
{
    int sum=0;
    while(i>0)
    {
        sum+=c[i];
        i-=lowbit(i);
    }
    return sum;
}
int main()
{
    while(~scanf("%d%d%d",&n,&x,&y))
    {
        memset(c,0,sizeof(c));
        long long int ans=0;
        for(int i=1;i<=n;i++)
        {
            int a;
            scanf("%d",&a);
            insert_num(a,1);
            ans+=i-getsum(a);
        }
        printf("%lld\n",ans*min(x,y));
    }
    return 0;
}

第三种方法:因为上一个记录数字大小位置的方法是通过数组的下标,数据太大的时候,开不了太大的数组,所以这次通过数组order来记录大小位置,离散化。(树状数组+离散化 ac

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e5+5;//范围
int n,x,y;
struct node
{
    int data;//原始数据
    int order;//下标
}a[maxn];
int c[maxn];
int aa[maxn];//离散化
int lowbit(int x)
{
    return x&(-x);
}
void updata(int i,int x)
{
    while(i<=n)
    {
        c[i]+=x;
        i+=lowbit(i);
    }
    return ;
}
int getsum(int i)
{
    int sum=0;
    while(i>0)
    {
        sum+=c[i];
        i-=lowbit(i);
    }
    return sum;
}
bool cmp(node a,node b)
{
    if(a.data==b.data)
        return a.order<b.order;
    return a.data<b.data;
}
int main()
{
    while(~scanf("%d%d%d",&n,&x,&y))
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i].data);
            a[i].order=i;
        }
        sort(a+1,a+1+n,cmp);
        for(int i=1;i<=n;i++)
            aa[a[i].order]=i;
        memset(c,0,sizeof(c));
        long long int ans=0;
        for(int i=1;i<=n;i++)
        {
            updata(aa[i],1);
            ans+=i-getsum(aa[i]);
        }
        printf("%lld\n",ans*min(x,y));
    }
    return 0;
}

总结:1 这次看错题了,把逆序数理解成相邻逆置的数了

            2 这次学了三种求逆序数的方法,也算是进步了

猜你喜欢

转载自blog.csdn.net/qq_40306845/article/details/81229188