题解:洛谷P3755 [CQOI2017]老C的任务

题目:P3755 [CQOI2017]老C的任务

题目大意:给定N个带权值的点,M个询问,求每个询问中矩阵内点权之和。

数据范围:N,M <= 1e5,点坐标及权值均在int范围内

标签:树状数组,离散化,离线,扫描线

思路梳理:

如果数据范围小一点,那么这就是一道二维树状数组模板题。

我们不妨回忆一下,二维树状数组的查询操作:

    res = query(X2,Y2) - query(X2,Y1 - 1) - query(X1 - 1,Y2) + query(X1 - 1,Y1 - 1);

类似地,我们也可以把本道题的询问拆成四个矩形来解决,记这些询问为新询问。

那么问题就转化为,如何求出给定矩阵的前缀和(实际上就是最小坐标一直到x,y组成的矩阵)。

但是数据范围过大,离散化后只允许开下一维的空间。怎么办?

考虑到没有强制在线,不妨将其离线,这样就可以有序化解决。

为什么有序化之后就可以解决了呢?

考虑扫描线。将给出点的坐标进行排序,一行一行进行扫描。
这样的话,扫描完第i行后,树状数组内存储的就是前i行的前缀和。

此时可以回答此行及以前的新询问。

接下来,解决如何离散化的问题。

考虑需要用到坐标的地方:

①更新:每个点的坐标将用于更新;

②查询:每个新询问的坐标将用于查询。

所以,我们不妨将这两组信息一起进行离散化,便于此后的查询操作。

至此,问题基本已经解决。

我们从头到尾梳理一遍代码的思路:

第一步,读入点坐标及询问坐标,将点坐标进行排序,将询问拆成四个新询问后进行排序;

第二步,将排序后的x坐标存进一个新的数组,注意保留原来的id及类型(点or询问)。排序后,将这些离散化后的x坐标存进数组,后期直接调用;

第三步,一行一行进行扫描,在扫描新点之前,回答答案已经确认的询问;

第四步,输出。

国际惯例,再考虑一下几个细节:

①数据类型?(被坑了不知多少次TAT)

虽然题目的信息都在int范围内,但要是数据一大,随便加一加马上就爆了。所以,必须开long long

②数组空间?

着重考虑易出错的几个数组:树状数组、新询问数组、x坐标数组。

新询问已经扩大到原询问的4倍。此外,离散化是针对新询问点坐标而言,故需开到N + (M<<2)(如果使用位运算,注意位运算的优先级比+-运算要低,所以要括号

③树状数组循环上界?(因为这个,一开始导致一些答案没被统计进去…)

应该是离散后的最大点坐标,为N + 4 * M。

完整代码:

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

#define int long long//debug

inline int read(){
	char c=getchar(); int f=1; int num=0;
	while (c>'9'||c<'0'){if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9'){num=(num<<1)+(num<<3)+(c^48);c=getchar();}
	return num*f;
}

inline void write(long long x, char c) {
    if(x==0){putchar('0'); putchar(c); return;}
    if(x<0){putchar('-'); x=-x;}
    int len=0; char buf[20];
    while(x){buf[++len]=x%10LL+48; x/=10LL;}
    while(len) putchar(buf[len--]);
    putchar(c);
    return;
}

const int N = 1e5 + 10,M = 1e5 + 10;

int n,m;
struct node{
	int x,y,p;
	bool operator < (const node & a) const{
		return y < a.y || y == a.y && x < a.x;
	}
}sta[N];
struct nodex{
	int xx,id,te;
	bool operator < (const nodex & a) const{
		return xx < a.xx;
	}
}a[N + M * 4];
int tox1[N],tox2[M * 4];
struct nodeq{
	int x,y,id,od;
	bool operator < (const nodeq & a) const{
		return y < a.y || y == a.y && x < a.x;
	}
}q[M<<2];
int res[M];
int cntq,CNT;
int tr[N + 4 * M];//debug:空间要开够
 
void modify(int x,int p){
	for(;x <= CNT;x += x & (-x)) //debug:循环上界
		tr[x] += p;
}

int query(int x){
	int sum = 0;
	for(;x > 0;x -= x & (-x)) sum += tr[x];
	return sum;
}

int tl[5] = {0,1,-1,-1,1};

signed main(){
	n = read();m = read();
	for(int i = 1;i <= n;i++)
		sta[i].x = read(),sta[i].y = read(),sta[i].p = read();
	sort(sta + 1,sta + n + 1);
	for(int i = 1;i <= n;i++)
		a[i].xx = sta[i].x,a[i].id = i,a[i].te = 1;
	for(int i = 1;i <= m;i++){
		int X1,Y1,X2,Y2;
		X1 = read();Y1 = read();X2 = read();Y2 = read();
		q[++cntq] = (nodeq){X1 - 1,Y1 - 1,i,1};
		q[++cntq] = (nodeq){X1 - 1,Y2,i,2};
		q[++cntq] = (nodeq){X2,Y1 - 1,i,3};//debug:注意-1
		q[++cntq] = (nodeq){X2,Y2,i,4};
	}
	sort(q + 1,q + cntq + 1);
	for(int i = 1;i <= cntq;i++)
		a[n + i].xx = q[i].x,a[n + i].id = i,a[n + i].te = 2;
	sort(a + 1,a + n + cntq + 1);
	CNT = 1;//debug:注意不能从0开始,否则树状数组会死循环
	for(int i = 1;i <= n + cntq;i++){
		if(i > 1 && a[i].xx > a[i - 1].xx) CNT++;
		if(a[i].te == 1) tox1[a[i].id] = CNT;
		else tox2[a[i].id] = CNT;
	}
	int cnt = 0;
	for(int i = 1;i <= n;i++){
		while(cnt + 1 <= cntq && q[cnt + 1].y < sta[i].y){
			cnt++;
			res[q[cnt].id] += query(tox2[cnt]) * tl[q[cnt].od];
		}//debug
		modify(tox1[i],sta[i].p);
	}
	cnt++;//debug:cnt的新询问已经统计了,要+1才是还没统计的询问
	for(;cnt <= cntq;cnt++)
		res[q[cnt].id] += query(tox2[cnt]) * tl[q[cnt].od];
	for(int i = 1;i <= m;i++)
		write(res[i],'\n');
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值