301 (div.2) E. Infinite Inversions

本文介绍了一种使用树状数组(Binary Indexed Tree, BIT)高效计算数组经过若干次元素交换后逆序对数量的方法。通过两个关键步骤:一是统计不在原位置的元素中小于当前元素的数量;二是统计仍在原位置的小于当前元素的数量,最终得到逆序对总数。

1.题目描述:点击打开链接

2.解题思路:本题要求找出经过若干次交换后的数组中逆序对的个数。可以利用树状数组(BIT)解决。计数时可以分为两部分来分别统计。第一部分是统计位置i右边的所有不在原位置的元素中,小于rk[i]的个数。注意:这一部分统计的都是位置发生过变动的元素。第二部分统计位置i右边中仍在原位置的元素中,小于rk[i]的个数。接下来我们考虑如何快速求解这两部分的个数。

第一部分:因为这部分统计的元素均是不在原位置的元素。因此可以考虑用BIT求解。这是BIT的一个经典运用,务必要理解这种方法的原理:假设一共有cnt个不同的位置,从后往前依次考虑每个位置rk[i],如果设x[rk[i]]表示是否存在rk[i]这个元素,存在则设为1,否则设为0,那么所有小于rk[i]的元素就是x[rk[i]-1]+x[rk[i]-2]+...+x[2]+x[1]。而这正是BIT的一个经典的用法——求前缀和!由于是从后往前扫描的,因此凡是被标记过的肯定是位于i右边的元素!这里引入x数组只是为了便于理解,实际上只有s数组用来统计每个结点的和。

第二部分:举例解释如何计算这一部分。我们考虑这样的一个数组:1,2,3,4,5,6 。执行操作2,6,那么数组变为:1,6,3,4,5,2。再执行操作1,4,那么数组变为4,6,3,1,5,2.这时,我们考察元素6的第二部分的逆序对数。可以发现,仍在原位置的只有3,5。因此对于元素6,第二部分逆序对数是2。这个数我们也可以这样求解得到:提取出发生了位置变化的元素,即4,6,1,2。对其排序得到:1,2,4,6。在这个数组中,可以利用递推式sum[i]=sum[i-1]+x[i]-x[i-1]-1求出区间(0,i)之间小于x[i]且仍在原位置的元素的个数,不难得到sum[4]=2(x[4]=6),sum[2]=0(x[2]=2),所以元素6的第二部分逆序个数为sum[4]-sum[2],即2-0=2个。

这样,只需要将两部分得到的逆序对数相加,即可得到离散化后下标i表示的位置rk[i]的逆序对数。累加所有的逆序对数即可得到答案。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<queue>

using namespace std;

int a[200005];
int b[200005];

int c[200005];
int d[200005];

int lb(int x)
{
    return x&(-x);
}

struct node
{
    int pos,num;
}p[200005];

bool cmp(node x,node y)
{
    return x.pos<y.pos;
}

void add(int x)
{
    while(x<=200000)
    {
        c[x]+=1;
        x+=lb(x);
    }
}

int gt(int x)
{
    int ans=0;
    while(x>=1)
    {
        ans+=c[x];
        x-=lb(x);
    }

    return ans;
}


int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(c,0,sizeof(c));

        int cnt1=0;
        int cnt2=0;
        map<int,int>mp;
        for(int i=0; i<n; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            a[cnt1++]=u;
            a[cnt1++]=v;
            //   if(!(mp[u]==0&&mp[v]==0))

            if(mp[u]==0)
                mp[u]=u;
            if(mp[v]==0)
                mp[v]=v;

            swap(mp[u],mp[v]);

        }


        map<int,int>::iterator it;

        it=mp.begin();
        while(it!=mp.end())
        {
            int pos=it->first;
            int num=it->second;
            p[cnt2].pos=pos;
            p[cnt2].num=num;
            it++;
            cnt2++;
        }

        sort(a,a+cnt1);
        cnt1=unique(a,a+cnt1)-a;

        sort(p,p+cnt2,cmp);

    //   cout<<cnt1<<endl;
    //   for(int i=0;i<cnt1;i++)
    //   cout<<a[i]<<" ";

    long long ans=0;
    for(int i=0;i<cnt2;i++)
    {
        b[i]=upper_bound(a,a+cnt1,p[i].num)-a+1;
        int les=gt(b[i]);
        ans+=(i-les);
        add(b[i]);
    }
    int cnt3=0;
    for(int i=0;i<cnt2;i++)
    {
        d[i]=p[i].pos;
    }

    //cout<<ans<<endl;
    for(int i=0;i<cnt2;i++)
    {
        int x=p[i].pos;
        int y=p[i].num;

        int id1=lower_bound(d,d+cnt2,x)-d;
        int id2=lower_bound(d,d+cnt2,y)-d;

        if(x!=y)
        {
            ans+=(abs(y-x)-abs(id1-id2));
            //ans++;
        }
    }

    printf("%lld\n",ans);

    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值