关闭

BZOJ 2141 排队 分块+树状数组,详细题解

261人阅读 评论(0) 收藏 举报
分类:

Description

排排坐,吃果果,生果甜嗦嗦,大家笑呵呵。你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和。红星幼儿园的小朋友们排起了长长地队伍,准备吃果果。不过因为小朋友们的身高有所区别,排成的队伍高低错乱,极不美观。设第i个小朋友的身高为hi,我们定义一个序列的杂乱程度为:满足ihj的(i,j)数量。幼儿园阿姨每次会选出两个小朋友,交换他们的位置,请你帮忙计算出每次交换后,序列的杂乱程度。为方便幼儿园阿姨统计,在未进行任何交换操作时,你也应该输出该序列的杂乱程度。
Input

第一行为一个正整数n,表示小朋友的数量;第二行包含n个由空格分隔的正整数h1,h2,…,hn,依次表示初始队列中小朋友的身高;第三行为一个正整数m,表示交换操作的次数;以下m行每行包含两个正整数ai和bi¬,表示交换位置ai与位置bi的小朋友。
Output

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

3

130 150 140

2

2 3

1 3

Sample Output
1

0

3

【样例说明】

未进行任何操作时,(2,3)满足条件;

操作1结束后,序列为130 140 150,不存在满足ihj的(i,j)对;

操作2结束后,序列为150 140 130,(1,2),(1,3),(2,3)共3对满足条件的(i,j)。

【数据规模和约定】

对于100%的数据,1≤m≤2*103,1≤n≤2*104,1≤hi≤109,ai≠bi,1≤ai,bi≤n。

解法:

应一位OI小朋友的要求,写一个这个题目的详细题解。

首先说一下逆序数,所谓逆序数就是满足i小于j,并且满足a[i]>a[j]的数的对数。显然求逆序数对数,可以直接暴

力,但是这样的复杂度是O(n^2)的,另外一种是用树状数组求,我们可以把数一个个插入到树状数组中,

每插入一个数, 统计比他小的数的个数,对应的逆序为 i- getsum( data[i] ),其中 i 为当前已经插入的数的

个数, getsum( data[i] )为比 data[i] 小的数的个数,i- getsum( data[i] ) 即比 data[i] 大的个数, 即逆序

的个数。最后需要把所有逆序数求和,就是在插入的过程中边插入边求和。可以自己在纸上模拟一下这个过

程,这里就不过于纠结了。然后一般求逆序数的题目,数会比较大,所以在之前加一个离散化就好了,离散化之后在做树状数组就好了,这个算法的复杂度是O(nlogn)的。

这个题, 显然 首先离散化,然后分块,对于每块建立一个树状数组,保存这个块中的所有元素

然后对于每个询问(x,y) (x小于y) 两侧的数是没有影响的,区间(x,y)的数a[i]讨论如下:

a[i]小于a[x] –ans

a[i]大于a[x] ++ans

a[i]小于a[y] ++ans

a[i]大于a[y] –ans

然后对于块中的树状数组处理,块外的暴力

然后注意有重复元素

复杂度O(sqrt(n)*log(n))

其实这个题树套树的复杂度是O(logn*logn)理论上是比分块快的,但是实际上这个题分块比树套树快到不知道

哪里去了。

//BZOJ 2141

#include <bits/stdc++.h>
using namespace std;

/// a[i] < a[x] --ans
/// a[i] > a[x] ++ans
/// a[i] < a[y] ++ans
/// a[i] > a[y] --ans

struct node{
    int h, id;//h代表高度,id代表在原来数组的值
    node(){}
    node(int h, int id):h(h),id(id){}
}a[20010];

int v[20010], top, tree[150][20010];
//v和top是用来离散化的,tree[i][j]代表的是第i块的树状数组,由于最多分到sqrt(n)块,150够了
int n, m, block;

bool cmp1(node a, node b){//先按高度排序,离散化
    return a.h < b.h;
}

bool cmp2(node a, node b){//再按照id排序,变回原来数组的位置
    return a.id < b.id;
}

void update(int p, int x, int a){//代表更新第p块的树状数组的值
    for(int i=x; i<=top;i+=i&-i){
        tree[p][i] += a;
    }
}

int query(int p,int x){//查询第p块的树状数组里面比x小的数的和
    int ans=0;
    for(int i=x;i;i-=i&-i){
        ans+=tree[p][i];
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    block = sqrt(n);
    for(int i=0;i<n;i++) scanf("%d",&a[i].h), a[i].id=i;
    //离散化
    sort(a,a+n,cmp1);
    for(int i=0;i<n;i++){
        if(a[i].h!=v[top]) v[++top]=a[i].h;
        a[i].h=top;
    }
    //按照id排序,但是现在高度变小了,可以把这个高度做成树状数组
    sort(a,a+n,cmp2);
    int ans = 0;
    //存答案
    for(int i=0;i<n;i++){
        //扫一遍数组
        for(int j=0;j<=i/block;j++) ans-= query(j,a[i].h);
        ans+=i;
        //参考上面树状数组求逆序数的过程
        update(i/block,a[i].h,1);
        把a[i]插入到i对应的块里面
    }
    printf("%d\n",ans);
    scanf("%d",&m);
    while(m--){
        int x,y;
        scanf("%d%d",&x,&y);
        x--,y--;
        //下标我从0开始,所以先减1
        if(x>y) swap(x,y);
        //注意区间大小不一定是左边比右边小
        if(x/block==y/block)//在同一块里面,我们按照上面的统计贡献的方法,直接暴力扫这一块,计算答案,块大小sqrt(n)所以复杂度不超过sqrt(n)
            for(int i=x+1;i<y;i++)
                ans += (a[i].h > a[x].h) + (a[i].h < a[y].h) - (a[i].h < a[x].h) - (a[i].h > a[y].h);
        else{
            //要是不在同一块,那么中间的整块,也就是块大小等于sqrt(n)的我们直接BIT求,端点不完整的两块, 仍然暴力扫描就可以了。
            for(int i=x/block+1;i<y/block;i++)
                ans += (block-query(i,a[x].h))+query(i,a[y].h-1)-query(i,a[x].h-1)-(block-query(i,a[y].h)); //这个统计贡献还是看上面
            for(int i=x+1; i<(x/block+1)*block;i++ )
                ans += (a[i].h>a[x].h)+(a[i].h<a[y].h)-(a[i].h<a[x].h)-(a[i].h>a[y].h);
            for(int i=y/block*block;i<y;i++)
                ans += (a[i].h>a[x].h) + (a[i].h<a[y].h)-(a[i].h < a[x].h)-(a[i].h > a[y].h);//同理
        }
        ans+=(a[x].h<a[y].h)-(a[x].h>a[y].h);
        //然后考虑本身两个数能否形成逆序数
        update(x/block,a[x].h,-1),update(y/block,a[y].h,-1);
        //要交换两个数,那么对这两个数所在块里的逆序数会产生影响,我们在这两个数的块里面去更新
        swap(a[x].h,a[y].h);
        //交换
        update(x/block,a[x].h,1),update(y/block,a[y].h,1);
        //和上面同理
        printf("%d\n",ans);
        //输出这次的答案
    }
    return 0;
}
0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

BZOJ 2141 排队 分块+树状数组

题目大意:给定一个序列,m次交换两个数,求初始逆序对数及每次交换后的逆序对数 首先离散化,分块,对于每块建立一个树状数组,保存这个块中的所有元素 然后对于每个询问(x,y) (x a[i] a...
  • PoPoQQQ
  • PoPoQQQ
  • 2014-10-22 11:43
  • 1950

BZOJ 3809 Gty的二逼妹子序列 莫队算法+分块

题目大意:给定一个序列,多次询问[l,r]区间内[a,b]范围内的数有多少 内存28MB,树套树可以歇菜了 首先普通的莫队+树状数组应该都能想到 这样做每次增加/删除一个点是O(logn)的 查询...
  • PoPoQQQ
  • PoPoQQQ
  • 2014-12-22 20:20
  • 2229

BZOJ 2141 排队 树套树

BZOJ 2141 排队 树套树
  • wzq_QwQ
  • wzq_QwQ
  • 2015-05-27 13:07
  • 1340

[BZOJ] 2141 - Atlantis - 排队 - 树状数组求逆序对 - 分块求区间比 k 小

2141: 排队 Time Limit: 4 Sec  Memory Limit: 259 MB Submit: 2506  Solved: 982 [Submit][Status][Discuss]...
  • yiranluoshen
  • yiranluoshen
  • 2017-12-17 23:08
  • 36

[BZOJ2141]排队(分块+树状数组求逆序对)

最纯粹的孤独,总是出自大师之门。
  • Clove_unique
  • Clove_unique
  • 2016-04-01 11:34
  • 599

BZOJ 2141 排队 [分块+树状数组]

BZOJ 2141 排队 [分块+树状数组]
  • ACTerminate
  • ACTerminate
  • 2017-07-24 17:00
  • 198

bzoj 2141: 排队 (树状数组套线段树)

题目描述传送门题目大意:给出一个序列,每次交换两个位置的数,求交换完后整个序列的逆序对数。题解对于一个位置会产生的逆序对数是他前面比他大的数+他后面比他小的数。 我们可以用树状数组套线段树维护一下,...
  • clover_hxy
  • clover_hxy
  • 2017-03-30 19:32
  • 156

BZOJ 2141排队(树状数组套Treap)

2141: 排队 Time Limit: 4 Sec  Memory Limit: 259 MB Submit: 798  Solved: 323 [Submit][Status][Discus...
  • u012969412
  • u012969412
  • 2015-04-14 19:53
  • 259

BZOJ 2141: 排队|树状数组套主席树

—————做的第一道树套树的题—————杂乱程度为逆序对的数量(ia[j]) 可以发现交换l和r对答案的影响只与区间(l,r)的数有关 交换l和r对答案作出的改变为: 加上(l,r)中大于a[l...
  • ws_yzy
  • ws_yzy
  • 2016-01-22 21:06
  • 643

【bzoj2141】 排队 树状数组+主席树

卧槽,为什么网上没有主席树的题解呀?!!! 貌似分块也可以做,但是代码会长一点吧,真心良心题不卡空间,O(nlog^2n)随便水。 结果发现自己忘考虑相同的数,一开始逆序对数求错了。 #...
  • u012288458
  • u012288458
  • 2015-08-07 19:22
  • 349
    个人资料
    • 访问:376186次
    • 积分:15870
    • 等级:
    • 排名:第780名
    • 原创:1254篇
    • 转载:13篇
    • 译文:0篇
    • 评论:61条
    文章分类