BZOJ 2141 排队 分块+树状数组,详细题解
2017-04-10 16:34
239 查看
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)理论上是比分块快的,但是实际上这个题分块比树套树快到不知道
哪里去了。
排排坐,吃果果,生果甜嗦嗦,大家笑呵呵。你一个,我一个,大的分给你,小的留给我,吃完果果唱支歌,大家乐和和。红星幼儿园的小朋友们排起了长长地队伍,准备吃果果。不过因为小朋友们的身高有所区别,排成的队伍高低错乱,极不美观。设第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: 排队 分块+树状数组
- [BZOJ] 2141 - Atlantis - 排队 - 树状数组求逆序对 - 分块求区间比 k 小
- 【bzoj2141】排队 分块+树状数组
- [BZOJ2141]排队(分块+树状数组求逆序对)
- Bzoj 2141: 排队 分块,逆序对,树状数组
- BZOJ 2141 排队 分块+树状数组
- BZOJ 2141排队(树状数组套Treap)
- BZOJ 2141 排队 [分块+树状数组]
- bzoj 2141: 排队 (树状数组套线段树)
- bzoj2141【分块+树状数组】
- BZOJ 2141 排队(树状数组套treap)
- 2141: 排队 分块+树状数组
- BZOJ - 2141 排队(树状数组套treap||分块)
- BZOJ 4765: 普通计算姬 [分块 树状数组 DFS序]
- 【BZOJ3289】【莫队分块+树状数组求逆序对】Mato的文件管理
- 【BZOJ2141】排队 树状数组+分块
- bzoj2141: 排队(树套树 或 分块)
- [BZOJ]3744: Gty的妹子序列 分块+树状数组+主席树
- Bzoj 3295: [Cqoi2011]动态逆序对 分块,树状数组,逆序对
- bzoj 2141 : 排队 分块