GDOI#443. 矩形的面积交[NOIP多校联考2019] 线段树+前缀和

题目链接:传送门

从前有一道水题,给你一个矩形,求另一个矩形和它的面积交。现在又有一道水题,给你 n n n个矩形,求另一个矩形和它们的面积交。 具体地,在 W ∗ L W*L WL的平面上有 n n n个互不相交的矩形,每个矩形的左下角点是 ( x 1 [ i ] , y 1 [ i ] ) (x_1[i],y_1[i]) (x1[i],y1[i]),右上角点是 ( x 2 [ i ] , y 2 [ i ] ) (x_2[i],y_2[i]) (x2[i],y2[i])。有 m m m组询问,对于每组询问,给出一个矩形,请输出这个矩形和给定的平面上 n n n个矩形的面积交的和。
n , m < = 500000 n,m<=500000 n,m<=500000

第一行包含两个整数 n,m,表示矩形数和询问数。
第二行两个整数 W 和 L,表示平面的长和宽。
接下来 n 行,每行四个整数,表示第 i 个矩形。
接下来 m 行,每行四个整数,表示询问的矩形。

n , m < = 5 ∗ 1 0 5 , W , L < = 5 ∗ 1 0 5 , 0 < = x 1 , x 2 < = W , 0 < = y 1 , y 2 < = L n,m<=5*10^5,W,L<=5*10^5,0<=x_1,x_2<=W,0<=y_1,y_2<=L n,m<=5105,W,L<=5105,0<=x1,x2<=W,0<=y1,y2<=L

先把一开始给出的 n n n个矩形映射到 y y y轴上,即对于一个矩形 ( x 1 , y 1 , x 2 , y 2 ) (x_1,y_1,x_2,y_2) (x1,y1,x2,y2),把它拆成 ( 0 , y 1 , x 2 , y 2 ) (0,y_1,x_2,y_2) (0,y1,x2,y2) ( 0 , y 1 , x 1 − 1 , y 2 ) (0,y_1,x_1-1,y_2) (0,y1,x11,y2)
如图,把黑色的矩形分解成红色、蓝色的矩形。
在这里插入图片描述
如果要算另一个矩形与黑色矩形的面积交,那么面积交为矩形与红色的面积交与矩形与蓝色的面积交之差(图就不画了,自己体会qwq)
根据这个性质,珂以再把询问的 m m m个矩形映射到 y y y轴上,再把面积交转化为两个面积交之差qwq。
然后问题就转化为 2 n 2n 2n个修改,每个修改中的矩形带一个系数(表示询问的矩形与这个矩形的面积交最后是加到答案中还是从答案中减去)
2 m 2m 2m个询问,每次询问一个矩形与之前矩形的面积交乘系数之和。
因为每个矩形的左端都在 y y y轴上,所以把矩形的右端横坐标 x x x叫做它的“高度”。
然后把所有矩形(包括修改和查询)按照高度从小到大排序,从左到右扫,用线段树维护(线段树的区间基于 y y y坐标,即是 y y y轴上的线段树)。
线段树不能直接维护区间和,因为查询的矩形高度小于修改的矩形高度时会很尴尬qwq:
(如图,黑色表示查询,红色表示修改,红色阴影区域面积无法通过区间修改/区间查询得出)
在这里插入图片描述

考虑另外的维护方式:
因为一个修改的矩形会分成两个矩形,所以用线段树维护有多少长度的线段上有修改( l e n len len)和修改的面积之和( s u m sum sum)。
并且规定分解出的矩形中,小的矩形系数为1,大的矩形系数为-1(即第一张图中,蓝色矩形系数为1,红色矩形系数为-1)
然后对每个询问的矩形,对它在 y y y轴上的两个映射点进行区间查询。
x x x表示询问矩形的高度,则 l e n ∗ x len*x lenx表示有修改的部分的总面积,而 s u m sum sum表示修改的面积之和。
又因为小的矩形系数为1,所以 l e n ∗ x − s u m len*x-sum lenxsum表示的就是这个矩形对答案的贡献。
给出一张图,珂以体会一下qwq:
在这里插入图片描述
图中假设黑色矩形是查询的矩形,两个红色矩形是没有被修改的部分
那么 l e n len len为两个红矩形在 y y y轴上投影的长度之和, s u m sum sum为两个红矩形面积之和。
由于红矩形出现在了图中,因此以红矩形在 y y y轴上投影为底对应的一开始给出的矩形,右端肯定在询问矩形的右端的右边。(否则两个红矩形已经被系数为-1的更新干掉了qwq)
这是一种珂能的一开始给出的矩形的情况(绿矩形表示一开始给出的 n n n个矩形):
在这里插入图片描述
于是得出结论: l e n ∗ x − s u m len*x-sum lenxsum为其对答案的贡献。
然后就是线段树模板……写丑了会被卡常qwq

毒瘤代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#define re register int
#define lc rt<<1
#define rc rt<<1|1
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<1)+(x<<3)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(const int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=500005;
const int INF=0x3f3f3f3f;
struct rect {
	int x;		//高度 
	int l,r;	//映射后的左右区间 
	int k;		//系数 
	int id;		//(询问的)编号 
} w[Size<<2];
inline bool comp(rect a,rect b) {
	return a.x<b.x;
}
int n,m,W,L;
struct node {
	int l,r;
	ll sum,len;		//总数,长度 
	int stag,ltag;
	inline int siz() {
		return r-l+1;
	}
	inline node operator += (const node b) {
		len+=b.len;
		sum+=b.sum;
		return *this;
	}
} tree[Size<<2];
inline void Pushup(int rt) {
	tree[rt].sum=tree[lc].sum+tree[rc].sum;
	tree[rt].len=tree[lc].len+tree[rc].len;
}
void Build(int l,int r,int rt) {
	tree[rt].l=l;
	tree[rt].r=r;
	if(l==r)	return;
	int mid=(l+r)>>1;
	Build(l,mid,lc);
	Build(mid+1,r,rc);
}
void Pushdown(int rt) {
	if(tree[rt].stag) {
		re tag=tree[rt].stag;
		tree[lc].stag+=tag;
		tree[rc].stag+=tag;
		tree[lc].sum+=tag*tree[lc].siz();
		tree[rc].sum+=tag*tree[rc].siz();
		tree[rt].stag=0;
	}
	if(tree[rt].ltag) {
		re tag=tree[rt].ltag;
		tree[lc].ltag+=tag;
		tree[rc].ltag+=tag;
		tree[lc].len+=tag*tree[lc].siz();
		tree[rc].len+=tag*tree[rc].siz();
		tree[rt].ltag=0;
	}
}
void Update(int l,int r,int v,int k,int rt) {
	if(l<=tree[rt].l && tree[rt].r<=r) {
		tree[rt].stag+=v*k;
		tree[rt].ltag+=k;
		tree[rt].sum+=v*k*tree[rt].siz();
		tree[rt].len+=k*tree[rt].siz();
		return;
	}
	Pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(l<=mid)	Update(l,r,v,k,lc);
	if(r>mid)	Update(l,r,v,k,rc);
	Pushup(rt);
}
node Query(int l,int r,int rt) {
	if(l<=tree[rt].l && tree[rt].r<=r) {
		return tree[rt];
	}
	Pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	node ans=(node){0,0,0,0,0,0};
	if(l<=mid)	ans+=Query(l,r,lc);
	if(r>mid)	ans+=Query(l,r,rc);
	return ans;
}
ll ans[Size];
int main() {
	n=read();
	m=read();
	W=read();
	L=read();
	int tot=0;
	for(re i=1; i<=n; i++) {
		int xa=read();
		int ya=read();
		int xb=read();
		int yb=read();
		w[++tot]=(rect){xa,ya+1,yb,1,0};	//面积->区间修改,左下角x,y坐标+1 
		w[++tot]=(rect){xb,ya+1,yb,-1,0};
	}
	for(re i=1; i<=m; i++) {
		int xa=read();
		int ya=read();
		int xb=read();
		int yb=read();
		w[++tot]=(rect){xa,ya+1,yb,-1,i};
		w[++tot]=(rect){xb,ya+1,yb,1,i};
	}
	Build(0,L,1);
	sort(w+1,w+1+tot,comp);
	for(re i=1; i<=tot; i++) {
		if(!w[i].id) {
			Update(w[i].l,w[i].r,w[i].x,w[i].k,1);
		} else {
			node now=Query(w[i].l,w[i].r,1);
			ans[w[i].id]+=w[i].k*(now.len*w[i].x-now.sum);
		}
	}
	for(re i=1; i<=m; i++) {
		printf("%lld\n",ans[i]);
	}
	return 0;
}
/*
1 1
6 6
1 1 5 3
0 0 6 2
*/
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值