洛谷1975-[国家集训队]排队-树套树

题目描述:

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

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

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

输入描述:

第一行为一个正整数n,表示小朋友的数量;

第二行包含n个由空格分隔的正整数h1,h2,…,hn,依次表示初始队列中小朋友的身高;

第三行为一个正整数m,表示交换操作的次数;

以下m行每行包含两个正整数ai和bi­,表示交换位置ai与位置bi的小朋友。

输出描述:

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

输入样例:

3
130 150 140
2
2 3
1 3

输出样例:

1
0
3

核心思想:

动态开点的线段树套树状数组(树套树)。

树套树模板传送门

tsum[i] 表示满足以下条件的人的个数
1、比第i个人靠前
2、比第i个人高
序列的杂乱程度ans为n个tsum[i]的和

对于某个i,我们通过树套树以log级的复杂度得到tsum[i],那么总时间复杂度为m*n*logn,会T。

对于每次操作,要将x处的人和y处的人互换位置。(不妨假定x<y)
我们将序列根据x和y分成5部分
[1,x-1],x,[x+1,y-1],y,[y+1,n]
这5部分特点如下:
1、交换x和y对部分1和部分5不会产生影响。
2、对于部分3内的任意一个i,在i之前的数只改变了一个(h[x]变为了h[y]),tsum[i]要么+1要么-1(当h[x]=h[y]时,tsum[i]不变)。处理每个i的复杂度为常量级O(1),部分3最多有n个数,复杂度为O(n)
3、由于x处和y处的高度改变了,x处和y处受到了很大的影响,用树套树log级更新和查询

初始建树的时间复杂度为n*logn。
总时间复杂度=n*logn+m*(n+logn)。

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2e4+20;
//tsum[i]表示位置在i之前且高度大于h[i]的人的个数
int n,h[N],tsum[N];
int cb,b[N];//用于离散
struct node{
    int z;
    node *zuo,*you;
    node():z(0),zuo(NULL),you(NULL){}
};
node *root[N];
int lowbit(int x)
{
    return x&(-x);
}
void change(node *&now,int l,int r,int k,int v)
{
    if(now==NULL)now=new node();
    now->z+=v;
    if(l==k&&r==k)return;
    int mid=(l+r)>>1;
    if(k<=mid)
        change(now->zuo,l,mid,k,v);
    else
        change(now->you,mid+1,r,k,v);
    return;
}
int sum(node *&now,int l,int r,int x,int y)
{
    if(now==NULL)return 0;
    if(l==x&&r==y)
        return now->z;
    int mid=(l+r)>>1;
    if(y<=mid)
        return sum(now->zuo,l,mid,x,y);
    if(x>mid)
        return sum(now->you,mid+1,r,x,y);
    return sum(now->zuo,l,mid,x,mid)+sum(now->you,mid+1,r,mid+1,y);
}
int getsum(int y,int l)
{
    int ans=0;
    for(int i=y;i>0;i-=lowbit(i))
        ans+=sum(root[i],1,cb,l,cb);
    return ans;
}
int getid(int x)
{
    return lower_bound(b+1,b+cb+1,x)-b;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&h[i]);
        b[i]=h[i];
    }
    sort(b+1,b+n+1);
    cb=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++)
        h[i]=getid(h[i]);
    //初始建树
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j+=lowbit(j))
            change(root[j],1,cb,h[i],1);
    int m,x,y,ans=0;
    scanf("%d",&m);
    for(int i=2;i<=n;i++)
    {
        tsum[i]=getsum(i-1,h[i]+1);
        ans+=tsum[i];
    }
    printf("%d\n",ans);
    //m次操作
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&x,&y);
        //相等不必交换
        if(h[x]==h[y])
        {
            printf("%d\n",ans);
            continue;
        }
        if(x>y)
            swap(x,y);
        //更新[x+1,y-1]
        int xi=min(h[x],h[y]),da=max(h[x],h[y]),d=h[x]>h[y]?-1:1;
        for(int i=x+1;i<y;i++)
            if(h[i]>=xi&&h[i]<da)
                tsum[i]+=d;
        //更新x处和y处
        for(int j=x;j<=n;j+=lowbit(j))
            change(root[j],1,cb,h[x],-1);
        for(int j=y;j<=n;j+=lowbit(j))
            change(root[j],1,cb,h[y],-1);
        swap(h[x],h[y]);
        for(int j=x;j<=n;j+=lowbit(j))
            change(root[j],1,cb,h[x],1);
        for(int j=y;j<=n;j+=lowbit(j))
            change(root[j],1,cb,h[y],1);
        tsum[x]=getsum(x-1,h[x]+1);
        tsum[y]=getsum(y-1,h[y]+1);
        //求和输出ans
        ans=0;
        for(int i=2;i<=n;i++)
            ans+=tsum[i];
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值