6681. 【2020.06.02省选模拟】图

题目

给出一个无重边、自环的平面图。需要回答若干个询问,每次询问给出一个简单环,求这个简单环形成的多边形内部有多少个点(包括边界上)。
n ≤ 1 e 5 , m ≤ 3 e 5 , Q ≤ 3 e 5 n\leq 1e5,m\leq 3e5,Q \leq 3e5 n1e5,m3e5,Q3e5
平面图无割点、并且对应着至少一个AC自动机?
尽管不知道这条限制有个卵用。


正解

表示比赛的时候基本上没有思考过。
这题暴力似乎还是可以写的,判断一个点在多边形内部,可以将这个点和每个顶点连边,极角排序,将排序后极角相邻的边之间的逆时针夹角求和。如果最终求和的结果为 2 π 2\pi 2π,则在多边形内。
或者也可以用射线法。

至于正解,这题有好多种做法:
先说神仙题解做法。
题解是建立一个汇点,放到最左边,与最左边的一个点连边。
接下来以这个汇点为根建一棵生成树。
现在看成这样一个模型:每个点上都带着一个流量,这个流量沿着父亲边流出。最终所有的流量都流入汇点。
这样对于每个点而言, F 出 − F 入 = 1 F_出-F_入=1 FF=1(出去的流量减进来的流量)
建出生成树之后 F 出 F_出 F就是子树大小。
对于一个多边形而言,它内部的点也是 F 出 − F 入 F_出-F_入 FF。道理可以如此理解:从外边流入多边形的流量,最终会出去;而起源于里面的流量,会流出多边形外。
结合平面图的性质,这个东西看起来似乎挺显然。但是不知道怎么证。
于是计算贡献的时候,枚举每个点,如果出边在多边形外,那么加上它的贡献;如果入边在多边形外,就减去它的贡献。将入边极角排序,前缀和+二分就可以快速地入边的贡献。
于是时间复杂度就是 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)

然后是大佬们提供的做法:
一个是平面图转成对偶图。原多边形中对应的边在对偶图中删掉,于是对偶图中间会出现一个独立出整体的连通块。计算出这个连通块的点数和边数,用平面图欧拉公式来计算出区域数。
于是这个问题就变成了:对于一个图,支持询问删去一些边之后,某个点所在连通块中的点数和边数。
预处理出每条边出现的时间,线段树分治+并查集随便搞搞。
时间复杂度 O ( n lg ⁡ 2 n ) O(n \lg^2 n) O(nlg2n)
套用之前某毒瘤题的思路可以优化到 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)(线段树上递归的时候将有关的节点建虚树,其它的点缩起来)。

另一个是考虑射线法。首先为了防止出题人卡,先给每个点随机转一个角度。
每个点向上做一条射线,如果穿过了多边形的边奇数次,它就在多边形内。
假设多边形是按照逆时针顺序建的,这样将从右边往左边的边的贡献记为 + 1 +1 +1,左边往右边的边的贡献记为 − 1 -1 1。于是一个点的贡献就是射线穿过的边的贡献和。
离线,然后按照 x x x轴扫描线。用平衡树按照纵坐标的相对顺序来记一下扫到的边。
扫到一个点的时候,计算这个点对那些边所属的多边形的贡献。将贡献挂在平衡树上的记录边的节点上。二分出这个点能贡献到的边的区间,然后区间修改之。
时间复杂度 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)


代码

代码中有个关于判断多边形是逆时针还是顺时针的问题。
gmh77:直接算有向面积,判断正负就可以了。
不用怕爆long long

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define N 100010
#define M 300010
#define INF 1000000000
#define ll long long
const double PI=acos(-1);
int n,m;
struct DOT{
	ll x,y;
	double theta(){return atan2(y,x)+PI;}
} d[N],O;
DOT operator+(DOT a,DOT b){return {a.x+b.x,a.y+b.y};}
DOT operator-(DOT a,DOT b){return {a.x-b.x,a.y-b.y};} 
ll cro(DOT a,DOT b){return a.x*b.y-a.y*b.x;}
double arc(double a,double b){return b-a<0?b-a+2*PI:b-a;}
bool between(double c,double a,double b){
	return abs(abs(arc(a,c))+abs(arc(c,b))-abs(arc(a,b)))<1e-8;
}
struct EDGE{
	int to;
	EDGE *las;
} e[M*2];
int ne;
struct Graph{
	EDGE *last[N];
	void link(int u,int v){
		e[ne]={v,last[u]};
		last[u]=e+ne++;
	}
} G;
bool vis[N];
int fa[N],deg[N];
void dfs(int x){
	vis[x]=1;
	for (EDGE *ei=G.last[x];ei;ei=ei->las)
		if (vis[ei->to]==0){
			fa[ei->to]=x;
			deg[x]++;
			dfs(ei->to);
		}
}
int pos[N],ls[N],cnt;
bool cmp(int a,int b){
	DOT p=d[a]-O,q=d[b]-O;
	return p.theta()<q.theta();
}
int siz[N];
int pre[N];
void init(int x){
	for (int i=0;i<deg[x];++i){
		int y=ls[pos[x]+i];
		init(y);
		siz[x]+=siz[y];
		pre[pos[x]+i]=siz[x];
	}
	siz[x]++;
}
int s[N];
int main(){
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		G.link(u,v),G.link(v,u);
	}
	int mnx=1;
	for (int i=1;i<=n;++i){
		scanf("%lld%lld",&d[i].x,&d[i].y);
		if (d[i].x<d[mnx].x)
			mnx=i;
	}
	d[0]={-INF-1,-INF-1};
	G.link(0,mnx),G.link(mnx,0);
	dfs(0);
	pos[0]=deg[0];
	for (int i=1;i<=n;++i)
		pos[i]=pos[i-1]+deg[i];
	for (int i=1;i<=n;++i)
		ls[--pos[fa[i]]]=i;
	for (int i=0;i<=n;++i){
		O=d[i];
		sort(ls+pos[i],ls+pos[i]+deg[i],cmp);
	}
	init(0);
	int Q;
	scanf("%d",&Q);
	while (Q--){
		int k;
		scanf("%d",&k);
		for (int i=0;i<k;++i)
			scanf("%d",&s[i]);
		ll S=cro(d[s[k-1]],d[s[0]]);
		for (int i=0;i<k-1;++i)
			S+=cro(d[s[i]],d[s[i+1]]);
		if (S<0)
			reverse(s,s+k);
		ll ans=0;
		for (int i=0;i<k;++i){
			int p=s[i],l=s[(i==0?k-1:i-1)],r=s[(i==k-1?0:i+1)];
			O=d[p];
			if (fa[p]!=l && fa[p]!=r && between((d[fa[p]]-O).theta(),(d[l]-O).theta(),(d[r]-O).theta()))
				ans+=siz[p];
			if (deg[p]==0)
				continue;
			int from=-1,to=-1;
			int L=0,R=deg[p]-1;
			while (L<=R){
				int mid=L+R>>1,q=ls[pos[p]+mid];
				if ((d[l]-O).theta()<(d[q]-O).theta())
					R=(from=mid)-1;
				else
					L=mid+1;
			}
			if (from==-1)
				from=0;
			int q=ls[pos[p]+from];
			if (!(q!=l && q!=r && between((d[q]-O).theta(),(d[l]-O).theta(),(d[r]-O).theta())))
				continue;
			L=0,R=deg[p]-1;
			while (L<=R){
				int mid=L+R>>1,q=ls[pos[p]+mid];
				if ((d[r]-O).theta()>(d[q]-O).theta())
					L=(to=mid)+1;
				else
					R=mid-1;
			}
			if (to==-1)
				to=deg[p]-1;
			if (from<=to)
				ans-=pre[pos[p]+to]-(from?pre[pos[p]+from-1]:0);
			else
				ans-=pre[pos[p]+to]+pre[pos[p]+deg[p]-1]-pre[pos[p]+from-1];
		}
		printf("%lld\n",ans);
	}
	return 0;
}
 

有一说一计算几何真的不好打。


总结

面对这些抖机灵题,比赛时是很难想出来的,所以还是要靠数据结构的硬实力。
另外,关于计算几何……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值