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

来源:互联网 发布:波音737max10数据 编辑:程序博客网 时间:2024/04/28 19:40

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] --ansstruct 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
原创粉丝点击