把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3776】[APIO2017] 斑斓之地(平面图欧拉公式)

点此看题面

  • 给定一张\(A\times B\)的网格图,把其中一条长度为\(n\)的路径标为黑色(可能自交),其余点为白色。
  • \(q\)次询问,每次求一个子矩阵中白色连通块的数量。
  • \(n\le10^5,A,B\le2\times10^5\)

平面图欧拉公式

对于一张连通的平面图,满足下面这个公式:

\[V-E+F=2 \]

其中\(V\)为点数,\(E\)为边数,\(F\)为面数。

由于\(F\)中的面数是包含外面的,因此我们可以设\(F'\)表示图形内部的面数,得到\(V-E+F'=1\)

对于树的问题我们常常利用\(V-E=1\)得出点数-边数=连通块数,其实就是\(F'=0\)的特殊情况。

对于这道题,在相邻的网格之间连边显然得到的是一张平面图,受树情况的启发就有结论:连通块数=点数-边数+面数。

贡献讨论+二维数点

由于白点数量很大,而黑点数量很少,这也就意味着不合法情况数很少,因此无论点数、边数、面数,我们都考虑用总情况数减去不合法情况数。

对于点数,非常简单,就是用\((x_2-x_1+1)\times(y_2-y_1+1)\)减去子矩形\((x_1,y_1)-(x_2,y_2)\)中的黑点数。

对于边数,分成上下连边和左右连边两部分,考虑把贡献记在上方/左方的点上,那么就分别为\((x_2-x_1)\times(y_2-y_1+1)\)减去子矩形\((x_1,y_1)-(x_2-1,y_2)\)的贡献以及\((x_2-x_1+1)\times(y_2-y_1)\)减去子矩形\((x_1,y_1)-(x_2,y_2-1)\)的贡献。

对于面数,一种情况是一组\(2\times2\)的四个白点间的面,把贡献记在左上方的点上,那么就是\((x_2-x_1)\times(y_2-y_1)\)减去子矩形\((x_1,y_1)-(x_2-1,y_2-1)\)的贡献;另一种情况是给定的子矩形完全包含了黑色路径且最外围连通,这样一来黑色路径围出的这一整部分也成为了外围连通块的一个面,这种情况只需将黑色路径的最小/最大横纵坐标与\(x_1,y_1,x_2,y_2\)比较一下即可判断。

不同的贡献计算都有一个共通的询问子矩形贡献和,就是一个二维数点问题,预处理建出主席树即可。

代码:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 200000
#define pb push_back
using namespace std;
int A,B,n,Rt[M+5][4];vector<int> p[M+5][4];
class ChairmanTree
{
	private:
		#define PT CI l=0,CI r=B
		#define LT l,mid
		#define RT mid+1,r
		int Nt;struct node {int V,S[2];}O[N*72];
	public:
		I void U(int& rt,CI x,PT)//单点修改
		{
			if(++(O[++Nt]=O[rt]).V,rt=Nt,l==r) return;RI mid=l+r>>1;
			x<=mid?U(O[rt].S[0],x,LT):U(O[rt].S[1],x,RT);
		}
		I int Q(CI rt,CI L,CI R,PT)//区间求和
		{
			if(!rt||L<=l&&r<=R) return O[rt].V;RI mid=l+r>>1;
			return (L<=mid?Q(O[rt].S[0],L,R,LT):0)+(R>mid?Q(O[rt].S[1],L,R,RT):0);
		}
}C[4];
int main()
{
	#define Mark(x,y) (xl=min(xl,x),xr=max(xr,x),yl=min(yl,y),yr=max(yr,y),\
		p[x][0].pb(y),p[x-1][1].pb(y),p[x][1].pb(y),p[x][2].pb(y-1),p[x][2].pb(y),\
		p[x-1][3].pb(y-1),p[x-1][3].pb(y),p[x][3].pb(y-1),p[x][3].pb(y))
	RI Qt,x,y,xl=1e9,xr=0,yl=1e9,yr=0;scanf("%d%d%d%d%d%d",&A,&B,&n,&Qt,&x,&y),Mark(x,y);
	RI i;char op;for(i=1;i<=n;++i) cin>>op,op=='N'?--x:(op=='S'?++x:(op=='W'?--y:++y)),Mark(x,y);
	RI j,k,sz;for(i=1;i<=A;++i) for(j=0;j^4;++j)
	{
		sort(p[i][j].begin(),p[i][j].end()),sz=unique(p[i][j].begin(),p[i][j].end())-p[i][j].begin();//去重
		for(Rt[i][j]=Rt[i-1][j],k=0;k^sz;++k) C[j].U(Rt[i][j],p[i][j][k]);//主席树上修改
	}
	#define Qry(op,x1,y1,x2,y2) (C[op].Q(Rt[x2][op],y1,y2)-C[op].Q(Rt[x1-1][op],y1,y2))//询问子矩形贡献和
	long long t;W(Qt--) scanf("%d%d%d%d",&i,&j,&x,&y),t=1LL*(x-i+1)*(y-j+1)-Qry(0,i,j,x,y),//点数
		i^x&&(t-=1LL*(x-i)*(y-j+1)-Qry(1,i,j,x-1,y)),j^y&&(t-=1LL*(x-i+1)*(y-j)-Qry(2,i,j,x,y-1)),//边数
		i^x&&j^y&&(t+=1LL*(x-i)*(y-j)-Qry(3,i,j,x-1,y-1)),i<xl&&xr<x&&j<yl&&yr<y&&++t,printf("%lld\n",t);//面数
	return 0;
}
posted @ 2021-06-02 09:30  TheLostWeak  阅读(259)  评论(0编辑  收藏  举报