BZOJ3295:[Cqoi2011]动态逆序对 (BIT套treap/CDQ分治+BIT)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3295


题目分析:这题裸的树套树啊。我们先算出原序列的逆序对数量,当一个数被删除的时候,逆序对的减小量=此时在它前面的比它大的数的个数+此时在它后面的比它小的数的个数。于是我们用一个树状数组套平衡树就可以搞定了。


CODE(BIT+treap):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int lg=20;
const long long M=1000000009;
const long long Times=998244353;
typedef long long LL;

struct Tnode
{
    int val,fix,Size;
    Tnode *lson,*rson;
    int Lsize() { return (lson? lson->Size:0); }
    int Rsize() { return (rson? rson->Size:0); }
    void Get_size() { Size=Lsize()+Rsize()+1; }
} tree[maxn*lg];
Tnode *bit[maxn];
int cur=-1;

LL seed,ans=0;

int a[maxn];
int p[maxn];
int n,m;

int Rand()
{
    seed=seed*Times%M;
    return (int)seed;
}

Tnode *New_node(int v)
{
    cur++;
    tree[cur].val=v;
    tree[cur].fix=Rand();
    tree[cur].Size=1;
    tree[cur].lson=tree[cur].rson=NULL;
    return tree+cur;
}

void Right_turn(Tnode *&P)
{
    Tnode *W=P->lson;
    P->lson=W->rson;
    W->rson=P;
    P=W;
    P->rson->Get_size();
    P->Get_size();
}

void Left_turn(Tnode *&P)
{
    Tnode *W=P->rson;
    P->rson=W->lson;
    W->lson=P;
    P=W;
    P->lson->Get_size();
    P->Get_size();
}

void Insert(Tnode *&P,int v)
{
    if (!P) P=New_node(v);
    else
    {
        if ( v<P->val )
        {
            Insert(P->lson,v);
            if ( P->lson->fix<P->fix ) Right_turn(P);
        }
        else
        {
            Insert(P->rson,v);
            if ( P->rson->fix<P->fix ) Left_turn(P);
        }
        P->Get_size();
    }
}

void Add(int x,int v)
{
    while (x<=n)
    {
        Insert(bit[x],v);
        x+=(x&(-x));
    }
}

int Query(Tnode *P,int v)
{
    if (!P) return 0;
    if ( P->val<v ) return P->Lsize()+1+Query(P->rson,v);
    return Query(P->lson,v);
}

int Sum(int x,int v,bool f)
{
    int sum=0;
    while (x)
    {
        int temp=Query(bit[x],v);
        if ( f && bit[x] ) temp=bit[x]->Size-temp;
        sum+=temp;
        x-=(x&(-x));
    }
    return sum;
}

void Delete(Tnode *&P,int v)
{
    if ( P->val==v )
        if ( !P->lson )
            if ( !P->rson ) P=NULL;
            else P=P->rson;
        else
            if ( !P->rson ) P=P->lson;
            else
                if ( P->lson->fix<P->rson->fix )
                    Right_turn(P),Delete(P->rson,v),P->Get_size();
                else Left_turn(P),Delete(P->lson,v),P->Get_size();
    else
        if ( v<P->val ) Delete(P->lson,v),P->Get_size();
        else Delete(P->rson,v),P->Get_size();
}

void Dec(int x,int v)
{
    while (x<=n)
    {
        Delete(bit[x],v);
        x+=(x&(-x));
    }
}

int main()
{
    freopen("3295.in","r",stdin);
    freopen("3295.out","w",stdout);

    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++) scanf("%d",&a[i]),p[ a[i] ]=i,bit[i]=NULL;
    seed=p[1];
    for (int i=1; i<=n; i++) Add(i,a[i]),ans+=(long long)( Sum(i-1,a[i],1) );

    for (int i=1; i<=m; i++)
    {
        printf("%lld\n",ans);
        int x;
        scanf("%d",&x);
        Dec(p[x],x);
        ans-=Sum(p[x]-1,x,1);
        ans-=( Sum(n,x,0)-Sum(p[x],x,0) );
    }

    return 0;
}

当然,我们也可以用CDQ分治。如果我们将此题的操作序列抽象出来,大概就是这样:QDQDQDQD,其中Q表示查询,D表示删数。我们记nx[i]为逆序对中有i这个值的逆序对数,则原数列的逆序对总数为 sum=ni=1nx[i]2 。一开始令每一个查询答案都为sum,令每一个删除x的操作都会使逆序对数减少nx[x]。然后我们将操作序列分为两半,很明显左半部分的D会对右半部分的Q与D都产生影响,所以我们应先递归左半部分,知道每一个D实际上删除了多少个逆序对。然后将右边的Q的答案减去左边的D删除的逆序对数,并将左边的D和右边的D堆在一起排一次序,用树状数组记录左边Delete的数会对右边的D所删除的逆序对数产生怎样的影响。时间复杂度 O(nlog2(n))

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxm=maxn>>1;
typedef long long LL;

struct data
{
    int id;
    bool Left;
} b[maxn];

int work[maxn];
LL num[maxn];

int bit[maxn];
LL nx[maxn];
LL sum=0;

int a[maxn];
int p[maxn];
int n,m;

int Sum(int x)
{
    int temp=0;
    while (x)
    {
        temp+=bit[x];
        x-=(x&(-x));
    }
    return temp;
}

void Add(int x,int v)
{
    while (x<=n)
    {
        bit[x]+=v;
        x+=(x&(-x));
    }
}

bool Comp1(data x,data y)
{
    return p[ work[ x.id ] ]<p[ work[ y.id ] ];
}

bool Comp2(data x,data y)
{
    return p[ work[ x.id ] ]>p[ work[ y.id ] ];
}

void CDQ(int L,int R)
{
    if (L==R) return;
    int mid=(L+R)>>1;
    CDQ(L,mid);

    int temp=0;
    LL dec=0;
    for (int i=L; i<=mid; i++)
        if (work[i])
        {
            temp++;
            b[temp].id=i;
            b[temp].Left=true;
            dec+=num[i];
        }
    for (int i=mid+1; i<=R; i++)
        if (work[i])
        {
            temp++;
            b[temp].id=i;
            b[temp].Left=false;
        }
        else num[i]-=dec;

    sort(b+1,b+temp+1,Comp1);
    int k=0;
    for (int i=1; i<=temp; i++)
    {
        int j=b[i].id;
        if (!b[i].Left) num[j]-=(k-Sum(work[j]));
        else k++,Add(work[j],1);
    }
    for (int i=1; i<=temp; i++)
        if (b[i].Left) Add(work[ b[i].id ],-1);

    sort(b+1,b+temp+1,Comp2);
    for (int i=1; i<=temp; i++)
    {
        int j=b[i].id;
        if (!b[i].Left) num[j]-=Sum(work[j]-1);
        else Add(work[j],1);
    }
    for (int i=1; i<=temp; i++)
        if (b[i].Left) Add(work[ b[i].id ],-1);

    CDQ(mid+1,R);
}

int main()
{
    freopen("3295.in","r",stdin);
    freopen("3295.out","w",stdout);

    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        p[ a[i] ]=i;
        nx[i]=(i-1)-Sum(a[i]);
        Add(a[i],1);
    }
    for (int i=1; i<=n; i++) bit[i]=0;
    for (int i=n; i>=1; i--)
    {
        nx[i]+=Sum(a[i]-1);
        Add(a[i],1);
    }
    for (int i=1; i<=n; i++) bit[i]=0,sum+=nx[i];
    sum>>=1;

    for (int i=1; i<=m; i++)
    {
        int x=(i<<1)-1;
        num[x]=sum;
        x++;
        scanf("%d",&work[x]);
        num[x]=nx[ p[ work[x] ] ];
    }

    CDQ(1,m<<1);

    for (int i=1; i<(m<<1); i+=2) printf("%lld\n",num[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值