HDU 4911 (树状数组+逆序数)

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

题目大意:最多可以交换K次,就最小逆序对数

解题思路

逆序数定理,当逆序对数大于0时,若ak<ak+1,那么交换后逆序对数+1,反之-1。

设原始序列最小逆序对数=cnt

那么,交换K次的最小逆序对数max(0,cnt-k)

在求原始序列最小逆序对数上,朴素暴力复杂度O(n^2)不可取

有以下两种O(nlogn)的方法:

①排序内计算:

主要是利用归并排序内的特性,即相邻两个归并序列逆序情况不改变,[5,4,2,1]到[4,5]、[1,2]

在排序纠正逆序之后,4和1,5和2的逆序情况没有改变。利用这个性质,只要在归并排序对两个子序列merge排序时,统计逆序对数即可。

即,边排序,边统计,假设left、right序列是递归传递过来的序列从0开始重新编号之后,初始偏移,i=j=0

当left[i]>right[j]出现逆序情况时,cnt+=(leftnum-i),即当前right[j]元素和left[i]及以后元素都构成逆序对。

归并后,递归继续merge更大的序列。统计复杂度=排序复杂度O(nlogn)

注意归并排序的写法,left尾和right尾要设为inf,这样后跑完的序列会直接和inf比较。

g#include "cstdio"
#include "algorithm"
#define LL long long
using namespace std;
int a[100005];
LL cnt=0;
void merge(int l,int m,int r)
{
    int lnum=m-l+1,rnum=r-m;
    int *LEFT=new int[lnum+1],*RIGHT=new int[rnum+1];
    for(int i=0;i<lnum;i++) LEFT[i]=a[l+i];
    for(int i=0;i<rnum;i++) RIGHT[i]=a[m+1+i];
    LEFT[lnum]=RIGHT[rnum]=0x3fffffff;
    int i=0,j=0;
    for(int k=l;k<=r;k++)
    {
        if(LEFT[i]<=RIGHT[j])
        {
            a[k]=LEFT[i];
            i++;
        }
        else
        {
            a[k]=RIGHT[j];
            j++;
            cnt+=(lnum-i);
        }
    }
}
void mergeSort(int l,int r)
{
    if(l<r)
    {
        int m=(r-l)/2+l;
        mergeSort(l,m);
        mergeSort(m+1,r);
        merge(l,m,r);
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        cnt=0;
        for(int i=0;i<n;i++) scanf("%d",&a[i]);
        mergeSort(0,n-1);
        printf("%I64d\n",max((LL)0,cnt-k));
    }
}

②树状数组:

很奇葩的方法。首先使用记录原始位置pos的排序,然后对排序后的元素进行离散化处理。

如序列5,1,1,离散化成2,1,1,树状数组sum[i]记录的是离散化位置被激活的次数,即add(Hash[i],1)

如离散化位置1,2,初始值[0,0], 首先按照输入顺序add离散化位置。

输入5,sum情况[0,1],那么树状数组getsum统计的是,在到此数的顺序数组上,被激活的个数。

用原始位置i-getsum,结果是,不含这个数,之前被激活的个数,即统计逆序情况。

如此时就是1,,这里由于1-1=0,即在5之前没有逆序对。

输入1,sum情况[1,1],getsum=1,i-getsum=1,有一个逆序对。[5,1],原因是5在1之前激活了。

输入1,sum情况[2,1],getsum=2, i-getsum=1,有一个逆序对。这里要对重复的数做add,因为重复的数,i增加了,

getsum也要对应的增加,不然,会和前面重复数的算重了,比如3-1=2,,就是算重了。

#include "cstdio"
#include "algorithm"
#include "cstring"
#include "map"
using namespace std;
#define LL long long
int sum[100005],n,k,val,N;
LL cnt;
int lowbit(int x) {return x&(-x);}
struct Num
{
    int val,pos;
    Num() {}
    Num(int val,int pos):val(val),pos(pos) {}
    bool operator < (const Num &a) const {return val<a.val;}
}a[100005];
LL getsum(int x)
{
    LL ret=0;
    while(x>0)
    {
        ret+=sum[x];
        x-=lowbit(x);
    }
    return ret;
}
void update(int x,int d)
{
    while(x<=N)
    {
        sum[x]+=d;
        x+=lowbit(x);
    }
}
int main()
{
    freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        memset(sum,0,sizeof(sum));
        map<LL,LL> Hash;
        cnt=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&val);
            a[i]=Num(val,i);
        }
        sort(a,a+n);
        int id=1;
        Hash[a[0].pos]=id;
        for(int i=1;i<n;i++) //离散化
        {
            if(a[i].val==a[i-1].val) Hash[a[i].pos]=id;
            else Hash[a[i].pos]=++id;
        }
        N=id;
        for(int i=0;i<n;i++)
        {
            update(Hash[i],1);
            cnt+=(i+1-getsum(Hash[i]));
        }
        printf("%I64d\n",max((LL)0,cnt-k));
    }
}

时间: 2024-10-08 16:26:41

HDU 4911 (树状数组+逆序数)的相关文章

树状数组+逆序数与顺序数——HDU 2492

对应HDU题目:点击打开链接 Ping pong Time Limit:3000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Submit Status Description N(3N20000) ping pong players live along a west-east street(consider the street as a line segment). Each player has a unique skil

Inversion (hdu 4911 树状数组 || 归并排序 求逆序对)

Inversion Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 2003    Accepted Submission(s): 787 Problem Description bobo has a sequence a1,a2,-,an. He is allowed to swap two adjacent numbers fo

hdu2838Cow Sorting(树状数组+逆序数)

题目链接:点击打开链接 题意描述:给定一个长度为100000的数组,每个元素范围在1~100000,且互不相同,交换其中的任意两个数需要花费的代价为两个数之和.问如何交换使数组有序,花费的代价最小? 解题思路: 1.显然我们知道,要使一个数组有序至少交换的次数(即必须要交换的次数)为数组中的逆序数 2.由于数组的长度比较大所以我们可以通过树状数组来统计结果 此处需要两个树状数组 第一个:记录小于等于某个值的元素的个数 第二个:记录小于等于某个值的元素的和 代码: #include <cstdio

HDU5196--DZY Loves Inversions 树状数组 逆序数

题意查询给定[L, R]区间内 逆序对数 ==k的子区间的个数. 我们只需要求出 子区间小于等于k的个数和小于等于k-1的个数,然后相减就得出答案了. 对于i(1≤i≤n),我们计算ri表示[i,ri]的逆序对数小于等于K,且ri的值最大.(ri对应代码中的cnt数组) 显然ri单调不降,我们可以通过用两个指针扫一遍,利用树状数组计算出r数组. 对于每个询问L,R,我们要计算的是∑i=LR[min(R,ri)−i+1] 由于ri具有单调性,那我们直接在上面二分即可,然后记一个前缀和(s数组).

POJ3067 树状数组+逆序数

设两线段为(x1,y1) ,(x2,y2), 若使两线段相交,需使x1<x2&&y1>y2||x1>x2&&y1<y2. 那么本题就变得很简单了,对东边点x从小到大排序,当x相等时对西边点y从小到大排序,每插入一条线段,就求一下逆序对数.总和即为答案. 代码如下: 1 #include <stdio.h> 2 #include <string.h> 3 #include <algorithm> 4 #define

HDU 1394 树状数组(逆序数)

Minimum Inversion Number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 10100    Accepted Submission(s): 6192 Problem Description The inversion number of a given number sequence a1, a2, ..., an

hdu 5193 分块 树状数组 逆序对

题意: 给出n个数,a1,a2,a3,...,an,给出m个修改,每个修改往数组的某个位置后面插入一个数,或者把某个位置上的数移除.求每次修改后逆序对的个数. 限制: 1 <= n,m <= 20000; 1 <= ai <= n 思路: 插入和删除用分块来处理,块与块之间用双向链表来维护,每一块用树状数组来求小于某个数的数有多少个. 外层可以使用分块维护下标,这样添加和删除元素的时候,也很方便,直接暴力.查找权值个数时,使用树状数组比较方便.内层通过树状数组维护权值. 每次更新即

HDU 4911 Inversion(归并排序求逆序数)

归并排序求逆序数,然后ans-k与0取一个最大值就可以了. 也可以用树状数组做,比赛的时候可能姿势不对,树状数组wa了.. Inversion Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 578    Accepted Submission(s): 249 Problem Description bobo has a seque

hdu 4911 Inversion(求逆序数)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4911 Inversion Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 528    Accepted Submission(s): 228 Problem Description bobo has a sequence a1,a2,