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

311人阅读 评论(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;
}
查看评论

BZOJ 2141 排队 分块+树状数组

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

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

卧槽,为什么网上没有主席树的题解呀?!!! 貌似分块也可以做,但是代码会长一点吧,真心良心题不卡空间,O(nlog^2n)随便水。 结果发现自己忘考虑相同的数,一开始逆序对数求错了。 #...
  • u012288458
  • u012288458
  • 2015-08-07 19:22:08
  • 413

【国家集训队2011】【BZOJ2141】排队

Description排排坐,吃果果,生果甜嗦嗦,大家笑呵呵。你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和。红星幼儿园的小朋友们排起了长长地队伍,准备吃果果。不过因为小朋友们的身...
  • CreationAugust
  • CreationAugust
  • 2015-09-19 10:59:23
  • 1727

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

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

BZOJ2141 排队(分块)

【题解】 分块,每块内部从小到大排序,每次询问,在完整的块内二分查找大于或小于 移动的值 的数的个数  复杂度: 初始求逆序对 O( n*sqrt(n) ) 询问 O( m*sqrt(n) )...
  • cjk_cjk
  • cjk_cjk
  • 2015-06-02 23:51:06
  • 304

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

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

[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:00
  • 72

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

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

bzoj2141: 排队

链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2141 题意:中文题。 分析:先将数组离散化一下,然后对于每一次交换考虑两个元素改变是对逆序...
  • Fsss_7
  • Fsss_7
  • 2016-01-01 01:56:26
  • 444

bzoj2141: 排队

Description 排排坐,吃果果,生果甜嗦嗦,大家笑呵呵。你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和。红星幼儿园的小朋友们排起了长长地队伍,准备吃果果。不过因为小朋...
  • lpf_as_a_oier
  • lpf_as_a_oier
  • 2016-01-29 20:50:04
  • 272
    个人资料
    专栏达人 持之以恒
    等级:
    访问量: 43万+
    积分: 1万+
    排名: 745
    博客专栏
    文章分类