BZOJ2141 || 洛谷P1975 [国家集训队]排队【线段树套treap】

Time Limit: 4 Sec
Memory Limit: 259 MB

Description

排排坐,吃果果,生果甜嗦嗦,大家笑呵呵。你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和。

红星幼儿园的小朋友们排起了长长地队伍,准备吃果果。不过因为小朋友们的身高有所区别,排成的队伍高低错乱,极不美观。设第i个小朋友的身高为hi,我们定义一个序列的杂乱程度为:满足 i<j i < j hi>hj h i > h j (i,j) ( i , j ) 数量。

幼儿园阿姨每次会选出两个小朋友,交换他们的位置,请你帮忙计算出每次交换后,序列的杂乱程度。为方便幼儿园阿姨统计,在未进行任何交换操作时,你也应该输出该序列的杂乱程度。

Input

第一行为一个正整数n,表示小朋友的数量;
第二行包含n个由空格分隔的正整数h1,h2,…,hn,依次表示初始队列中小朋友的身高;
第三行为一个正整数m,表示交换操作的次数;
以下m行每行包含两个正整数ai和bi,表示交换位置ai与位置bi的小朋友。
1m21031n21041hi109aibi1ai,bin 1 ≤ m ≤ 2 ∗ 10 3 , 1 ≤ n ≤ 2 ∗ 10 4 , 1 ≤ h i ≤ 10 9 , a i ≠ b i , 1 ≤ a i , b i ≤ n 。

Output

输出文件共m行,第i行一个正整数表示交换操作i结束后,序列的杂乱程度。


题目分析

本质就是动态逆序对

一开始先求出初始序列的逆序对数
对于每个 1<=i<n 1 <= i < n 求出 i 到 n区间内比 hi h i 小的数的个数总和
记为ans

每次交换了x和y位置的人
ans+= x+1到y区间内比 hy h y 小的数的个数
ans+= x到y-1区间内比 hx h x 大的数的个数

ans-= x+1到y区间内比 hy h y 大的数的个数
ans-= x到y-1区间内比 hx h x 小的数的个数

最后单独比较 hx h x hy h y
hx>hy h x > h y ,则令ans–
反之ans++

最后再分别修改x和y位置的值

特别的
如果 hx==hy h x == h y ,那么可以直接输出答案并跳过更新


#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
#define update(p) sum[p]=cnt[p]+sum[ch[p][0]]+sum[ch[p][1]];
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=500010;
int n,m;
int a[maxn],rt[maxn],val[maxn],tot;
int ch[maxn][2],rnd[maxn],sum[maxn],cnt[maxn];
lt ans;

void rotate(int &p,int d)
{
    int k=ch[p][d^1];
    ch[p][d^1]=ch[k][d];
    ch[k][d]=p;
    update(p); update(k);
    p=k;
}

void ins(int &p,int x)
{
    if(!p)
    {
        p=++tot; val[p]=x; rnd[p]=rand();
        sum[p]=cnt[p]=1;
        return;
    }
    if(val[p]==x){ ++sum[p]; ++cnt[p]; return;}
    int d=x<val[p] ?0:1;
    ins(ch[p][d],x);
    if(rnd[ch[p][d]]<rnd[p]) rotate(p,d^1);
    update(p);
}

void build(int s,int t,int p)
{
    for(int i=s;i<=t;++i) ins(rt[p],a[i]);
    if(s==t) return;
    int mid=s+t>>1;
    build(s,mid,p<<1); build(mid+1,t,p<<1|1); 
}

int rank_treap(int p,int x,int d)
{
    if(!p) return 0;
    int ss=sum[ch[p][d]];
    if(val[p]==x) return ss;
    else if((x<val[p]&&d==0)||(x>val[p]&&d==1)) return rank_treap(ch[p][d],x,d);
    else return ss+cnt[p]+rank_treap(ch[p][d^1],x,d);
}

int rank_seg(int ll,int rr,int s,int t,int p,int v,int d)
{
    if(ll<=s&&t<=rr) return rank_treap(rt[p],v,d);
    int mid=s+t>>1,cnt=0;
    if(ll<=mid) cnt+=rank_seg(ll,rr,s,mid,p<<1,v,d);
    if(rr>mid) cnt+=rank_seg(ll,rr,mid+1,t,p<<1|1,v,d);
    return cnt;
}

void del(int &p,int x)
{
    if(!p) return;
    if(val[p]==x)
    {
        if(cnt[p]>1){ --sum[p]; --cnt[p]; return;}
        else if(ch[p][0]*ch[p][1]==0) p=ch[p][0]+ch[p][1];
        else
        {
            int dd=rnd[ch[p][0]]<rnd[ch[p][1]] ?1:0;
            rotate(p,dd); del(ch[p][dd],x);
        } 
    }
    else if(x<val[p]) del(ch[p][0],x);
    else del(ch[p][1],x);
    if(p) update(p);
}

void modify(int s,int t,int p,int pos,int v)
{
    del(rt[p],a[pos]); ins(rt[p],v);
    if(s==t) return;
    int mid=s+t>>1;
    if(pos<=mid) modify(s,mid,p<<1,pos,v);
    else modify(mid+1,t,p<<1|1,pos,v);
}

int main()
{
    n=read();
    for(int i=1;i<=n;++i) a[i]=read();
    build(1,n,1);

    for(int i=1;i<n;++i)
    ans+=rank_seg(i,n,1,n,1,a[i],0);//初始逆序对数
    printf("%lld\n",ans);

    m=read();
    while(m--)
    {
        int x=read(),y=read(); if(x>y) swap(x,y);//注意左右大小区间
        if(a[x]==a[y]){ printf("%lld\n",ans); continue;}

        ans+=rank_seg(x+1,y,1,n,1,a[y],0)+rank_seg(x,y-1,1,n,1,a[x],1);
        ans-=rank_seg(x+1,y,1,n,1,a[y],1)+rank_seg(x,y-1,1,n,1,a[x],0);
        //0表示查询比该数小的个数,1反之

        modify(1,n,1,x,a[y]); modify(1,n,1,y,a[x]);
        swap(a[x],a[y]);
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值