UVa 11990 分桶法 + 平方分割

题意

传送门 UVa 11990

顺序删除数字可以用一维 BIT 查询逆序数,这里是随机位置删除数字,需要增加数字在数列中所处位置的 1 个维度, ( i , d a t [ i ] ) (i, dat[i]) (i,dat[i]) 代表索引为 i i i, 值为 d a t [ i ] dat[i] dat[i] 的数列上某一个数字。位于该数字对应的二维坐标点左上和右下的位置的点,都可与其构成逆序数对。

问题变成了如何在二维空间里高效查询某一矩形范围的点数,二维 BIT 实现简单,但空间会爆。这里采用分桶法 + 平方分割,考虑到每一次计算需对桶做求和,每一个桶维护当前列从第 0 行至当前行所代表矩形范围的点数,求和操作 O ( n 1 / 2 ) O(n^{1/2}) O(n1/2) 在列方向求和即可。每次增加一个点,就计算当前空间内能与其配成逆序数对的点数,更新总逆序数。

实现上逆着删除次序操作,即每次仅考虑增加点即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define min(a,b)    (((a) < (b)) ? (a) : (b))
#define max(a,b)    (((a) > (b)) ? (a) : (b))
#define abs(x)    ((x) < 0 ? -(x) : (x))
#define INF 0x3f3f3f3f
#define delta 0.85
#define eps 1e-3
#define PI 3.14159265358979323846
#define MAX_N 200005
#define MAX_M 100005
using namespace std;
typedef long long LL;
const int B_SIZE = 450;
int N, M;
int dat[MAX_N], id[MAX_N]; //数据与索引的映射
int qry[MAX_M];
bool mask[MAX_N], used[MAX_N]; //代表是否为需删除节点,是否已添加
int bucket[B_SIZE][B_SIZE];
LL res[MAX_M];

void add(int x, int y){
	int bx = x / B_SIZE, by = y / B_SIZE;
	for(int i = bx; i <= N / B_SIZE; i++) ++bucket[i][by];
}
// 求 [0, x] * [0, y] 区域总点数
int sum(int x, int y){
	int bx = (x + 1) / B_SIZE, by = (y + 1) / B_SIZE;
	int sum = 0;
	if(bx > 0){
		for(int i = 0; i < by; i++) sum += bucket[bx - 1][i];
	}
	for(int i = bx * B_SIZE; i <= x; i++){
		if(used[i] && dat[i] <= y) ++sum;
	}
	for(int i = by * B_SIZE; i <= y; i++){
		if(used[id[i]] && id[i] < bx * B_SIZE) ++sum;
	}
	return sum;
}
//求左上与右下区域点数
int cal(int x, int y){
	return sum(x, N) + sum(N, y) - 2 * sum(x, y);
}

int main(){
	while(~scanf("%d%d", &N, &M)){
		memset(bucket, 0, sizeof(bucket));
		memset(mask, 0, sizeof(mask));
		memset(used, 0, sizeof(used));
		for(int i = 1; i <= N; i++){
			scanf("%d", dat + i);
			id[dat[i]] = i;
		}
		for(int i = 0; i < M; i++){
			int n;
			scanf("%d", &n);
			mask[qry[i] = id[n]] = 1;
		}
		LL inv = 0;
		for(int i = 1; i <= N; i++){
			if(!mask[i]){
				inv += cal(i, dat[i]);
				add(i, dat[i]);
				used[i] = 1;
			}
		}
		for(int i = M - 1; i >= 0; i--){
			int x = qry[i], y = dat[x];
			inv += cal(x, y);
			res[i] = inv;
			add(x, y);
			used[x] = 1;
		}
		for(int i = 0; i < M; i++) printf("%lld\n", res[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值