首页 > 其他分享 >P9478 [NOI2023] 方格染色题解

P9478 [NOI2023] 方格染色题解

时间:2024-02-09 15:44:04浏览次数:28  
标签:int 题解 tot X2 NOI2023 P9478 对角线 Y1 X1

image

题解

对于行操作,列操作和对角线操作,实际上仅仅只是在对若干个矩形求面积并而已,这是裸的扫描线题,套用模板即可,此时注意到对角线操作实际上是 \(O(n)\) 量级的矩阵面积并,因此复杂度是 \(O(n\log q+q\log q)\) 的量级,只能获得 95 pts。

显然,面积并具有交换性,我们先做 \(O(q\log q)\) 的行与列矩阵的面积并,最后再看对角线,如果将对角线拆成 \(O(n)\) 量级的矩阵,不难发现在做面积并的时候,单位矩阵要么是被包含了,要么是独立的,看上去其实并不需要用线段树来维护,因而我们把对角线拆了再用线段树浪费了很多时间。再看到对角线的数量很少(在 95 pts 的做法中对角线操作的复杂度其实就考虑了这个事情),我们看看是否可以做一个简单的容斥?

在做完 \(O(q\log q)\) 的行与列矩阵的面积并后,我们考虑加入一条对角线,直接加 \(x_2-x_1+1\),那么我们必然会多统计一些点,怎么减掉呢?注意到,一条对角线与一个行矩阵或列矩阵最多只有一个交点,也就是说,多统计的点数是 \(O(q)\) 量级的,我们暴力的求解每一个行矩阵或列矩阵与对角线的交点数,如果这个复杂度对单次判断是 \(O(1)\) 的,那么我们就有了一个 \(O(q+q\log q)\) 的一个满分算法。

显然,这是 \(O(1)\) 的,本质上是在求一条一次函数与常函数的交点,只不过两个函数的定义域都有一定限制,我们只需要判断交点是否都在两个定义域之间即可。

但是我们仍然有可能多统计,具体有两方面,第一个,对角线之间可能重叠,我们可以在一开始就对对角线进行一次面积并,但我们仍然不需要线段树,由于本题对角线同向,两条线相交前提是两条线共线,只要截距相同,然后变成了可以 \(O(1)\) 解决的区间并,这里还是注意到对角线的数量是常数量级。第二个,一条对角线可能与多个行或列矩阵交于同一点,我们只需要减一次就够了,我们开一个 \(\text{map}\) 来标记已容斥过的点即可。

正确性和效率都得到了保证,可以开始码了,这里使用了动态开点的线段树,省去离散化的过程了。

AC code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+10;
int c,n,m,q,T,X1,Y1,X2,Y2,tot,len,cnt,sz,rt;
int ans,flag[N];
map<pair<int,int>,int>Map;
struct scan {
	int x,up,down,ty;
}L[N];
bool cmp(scan a,scan b) {
	return a.x<b.x;
}
struct Line {
	int X1,Y1,X2,Y2;
}L12[N],L3[N];
bool check1(Line a,Line b) {
	if(a.X1-a.Y1!=b.X1-b.Y1) return false;
	if(a.X2<b.X1||a.X1>b.X2) return false;
	return true;
}
bool check2(Line a,Line b,int op) {
	if(op==1) {
		int X=b.X1-b.Y1+a.Y1;
		if(Map[make_pair(X,a.Y1)]) return false;
		if(a.X1<=X&&X<=a.X2&&b.X1<=X&&X<=b.X2) {
			Map[make_pair(X,a.Y1)]=1;
			return true;
		}
		return false;
	}
	else {
		int Y=b.Y1-b.X1+a.X1;
		if(Map[make_pair(a.X1,Y)]) return false;
		if(a.Y1<=Y&&Y<=a.Y2&&b.Y1<=Y&&Y<=b.Y2) {
			Map[make_pair(a.X1,Y)]=1;
			return true;
		}
		return false;
	}
}
struct node {
	int ls,rs,len,cov;
}t[N<<2];
void pushup(int p,int L,int R) {
	if(t[p].cov) t[p].len=R-L+1;
	else t[p].len=t[t[p].ls].len+t[t[p].rs].len;
}
void update(int &p,int L,int R,int l,int r,int d) {
	if(!p) p=++sz;
	if(l<=L&&R<=r) {
		t[p].cov+=d;
		pushup(p,L,R);
		return;
	}
	int mid=L+R>>1;
	if(l<=mid) update(t[p].ls,L,mid,l,r,d);
	if(r>mid) update(t[p].rs,mid+1,R,l,r,d);
	pushup(p,L,R);
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>c>>n>>m>>q;
	for(int i=1;i<=q;i++) {
		cin>>T>>X1>>Y1>>X2>>Y2;
		if(T==1||T==2) {
			L[++tot].x=X1,L[tot].up=Y2,L[tot].down=Y1,L[tot].ty=1;
			L[++tot].x=X2+1,L[tot].up=Y2,L[tot].down=Y1,L[tot].ty=-1;
			L12[++cnt].X1=X1,L12[cnt].Y1=Y1,L12[cnt].X2=X2,L12[cnt].Y2=Y2;
		}
		if(T==3) L3[++len].X1=X1,L3[len].Y1=Y1,L3[len].X2=X2,L3[len].Y2=Y2;
	}
	sort(L+1,L+tot+1,cmp);
	for(int i=1;i<tot;i++) {
		update(rt,1,m,L[i].down,L[i].up,L[i].ty);
		ans+=t[rt].len*(L[i+1].x-L[i].x);
	}
	for(int i=1;i<len;i++) {
		if(!flag[i]) {
			for(int j=i+1;j<=len;j++) {
				if(!flag[j]&&check1(L3[i],L3[j])) {
					L3[i].X1=min(L3[i].X1,L3[j].X1);
					L3[i].Y1=min(L3[i].Y1,L3[j].Y1);
					L3[i].X2=max(L3[i].X2,L3[j].X2);
					L3[i].Y2=max(L3[i].Y2,L3[j].Y2);
					flag[j]=1;
				}
			}
		}
	}
	for(int i=1;i<=len;i++) {
		if(flag[i]) continue;
		ans+=L3[i].X2-L3[i].X1+1;
		for(int j=1;j<=cnt;j++) {
			if(L12[j].Y1==L12[j].Y2) {
				if(check2(L12[j],L3[i],1)) ans--;
			}
			if(L12[j].X1==L12[j].X2){
				if(check2(L12[j],L3[i],2)) ans--;
			}
		}
	}
	cout<<ans;
	return 0;
}

标签:int,题解,tot,X2,NOI2023,P9478,对角线,Y1,X1
From: https://www.cnblogs.com/2021hych/p/18012498

相关文章

  • uoj839 龙门题字 题解
    题目链接点击打开链接题目解法呜呜呜,我考场上只会\(60\),不会优化,没救了要求字典序最大,不难想到一位一位钦定,那么我们现在的问题是判断\(ans_1,...,ans_i\)是否合法,记\(ok_{i,j}\)表示第\(i\)个修改在第\(j\)位是否可行(即\(x_{i,j}\geans_j\)),同时记\(cnt_i\)为第......
  • 2024大试牛刀5题解
    鼠鼠我鸭没学过前缀和的自己去补一下,这里不过多赘述(其实是我不想写)以第二组数据为例:类型0100体重2565先计算出不使用魔法时鸭鸭的重量,当作基础值\(ori=5\)。然后来看看对一段区间使用魔法会造成什么效果:类型0100体重2565变化a+2-5......
  • ABC338G题解
    也许是一个简单一点的做法?假如我们要求一个表达式的值,我们求的顺序显然是,按照加号划分为若干段,段内只有数字和乘号,计算出段内结果后全部加起来。称一个只有数字和乘号的表达式是基本表达式,我们将每个表达式的贡献拆分为若干个基本表达式之和,那么我们就可以枚举每个基本表达式,算......
  • CF516D Drazil and Morning Exercise 题解
    Description给定一棵\(n\)个点的树,边有边权。定义\(f_x=\max_{i=1}^n\text{dist}(x,i)\)。\(q\)次询问最大的满足\(\max_{x\ins}f_x-\min_{x\ins}f_x\lel\)的连通块\(s\)包含的点数。\(n\le10^5\),\(q\le50\)。Solution这里\(f_u\)显然可以用换......
  • CF1771F Hossam and Range Minimum Query 题解
    题目链接:CF或者洛谷比较不错的题,出现奇数次出现的这种问题,比较容易想到一种运算,那就是异或和运算。如果一个区间上一个数出现偶数次,则它对于异或和的贡献为\(0\),那么很显然,我们维护下区间异或和即可判断一个区间上是否存在出现奇数次的数。然后我们注意到\(1\oplus2\oplu......
  • CF1861E Non-Intersecting Subpermutations 题解
    简要题意一个长度为\(n\)的元素在\([1,k]\)的整数序列\(a\)的价值定义如下。初始\(i=1\),如果\(a_{i\simi+k-1}\)包含了\(1\simk\)的所有整数,则价值加\(1\),然后令\(i=i+k\)。如果没有包含\(1\simk\)的所有整数则\(i=i+1\),直到\(i\geqn-k+2\)时结束。......
  • CF1863F Divide, XOR, and Conquer 题解
    简要题意你有两个指针\(l,r\)初始时\(l=1,r=n\)。你可以操作若干次知道\(l=r\)。每次操作你可以选择一个整数\(k\in[l,r)\),记\(x=\bigoplus_{i=l}^ka_i,y=\bigoplus_{i=k+1}^ra_i\)。如果\(x\leqy\),你可以令\(l=k+1\)。如果\(x\geqy\),你可以令\(r=k\)。......
  • P9663 Permutation on Tree 题解
    考虑枚举一个\(x\in[1,n)\),将\(\leqx\)的看作\(0\),\(>x\)的看作\(1\),那么一个排列的贡献实际上就是\(\sum_{x=1}^{n-1}\sum[[p_i\leqx]+[p_{i+1}>x]=1]\)。那么问题转变为一个给定一棵树,每一个点有权值\(0\)或\(1\),求所有排布方案的贡献之和。设\(f_x\)表示......
  • P9697 Canvas 题解
    首先观察到有一个关键性质是\(1\leqx_i,y_i\leq2\)。那么我们贪心的考虑我们肯定会将\((x,y)=(1,1)\)的在一开始操作,\((x,y)=(2,2)\)的最后操作。也就是说现在没有固定的是\((x,y)=(1,2)\)和\((x,y)=(2,1)\)的。我们不妨令\((x,y)=(1,2)\),然后连边\((l,r)\)。然......
  • P10013 Tree Topological Order Counting 题解
    首先题目里面写了每一个数都有权值,一般这种题只能去想求出每一个的具体方案数,那么也就是我们得求出\(h_{i,j}\)表示在所有合法拓扑序中\(a_i=j\)的方案数。一颗树的拓扑序数量是\(\dfrac{n!}{\prodsiz_i}\),相信大家都知道。因为我们需要保证这一棵树满足拓扑排序的条件,不......