Luogu P3801 红色的幻想乡

题目传送门

据说NOIP前写题解可以(mathtt{RP})++


20pts

我会暴力!

40pts

我会二维树状数组(线段树)!

AC

(n,m,q<=100000),二维肯定会炸。考虑每次修改都是修改一整行和一整列,可以分别对行和列建线段树,统计区间内有多少行、列被放过红雾。
因为我们用线段树统计了有多少行、列被修改过,但询问的是有多少点有红雾,所以答案应为(x imes len_n+y imes len_m),其中x,y表示区间内有红雾的行、列数,(len)表示对应的区间长度。
但这么做肯定是不对的,因为行列的交点被我们算了两次,而它们应该没有红雾。所以我们应用容斥原理,将它们减去就好了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
#define LL long long
using namespace std;
struct zzz{  //线段树统计有红雾的行、列
	int tree[100010<<2];
	inline void up(int p){
		tree[p]=tree[ls]+tree[rs];
	}
	void update(int l,int r,int p,int pos){
		if(l==r){
			tree[p]^=1; return ;  //因为在已经有红雾的行和列上放红雾会全部抵消,所以是xor操作
		}
		if(pos<=mid) update(l,mid,ls,pos);
		else update(mid+1,r,rs,pos);
		up(p);
	}
	int query(int l,int r,int p,int nl,int nr){
		int ans=0;
		if(nl<=l&&r<=nr) return tree[p];
		if(nl<=mid) ans+=query(l,mid,ls,nl,nr);
		if(nr>mid) ans+=query(mid+1,r,rs,nl,nr);
		return ans;
	}
}hang,lie;
int read(){
	int k=0; char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	  k=k*10+c-48, c=getchar();
	return k;
}
int main(){
	int n=read(),m=read(),q=read();
	for(int i=1;i<=q;i++){
		int opt=read();
		if(opt==1){
			int x=read(),y=read();
			hang.update(1,n,1,x);
			lie.update(1,n,1,y);
		}
		else{
			LL a=read(),b=read(),c=read(),d=read();
			LL hh=hang.query(1,n,1,a,c);
			LL ll=lie.query(1,n,1,b,d);
			LL ans=hh*(d-b+1)+ll*(c-a+1)-2*hh*ll;  //如上文式子所说,有红雾的行列数*行列长度-交点*2
			printf("%lld
",ans);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/morslin/p/11854976.html