6681. 【2020.06.02省選模擬】圖

題目

給出一個無重邊、自環的平面圖。需要回答若干個詢問,每次詢問給出一個簡單環,求這個簡單環形成的多邊形內部有多少個點(包括邊界上)。
n1e5,m3e5,Q3e5n\leq 1e5,m\leq 3e5,Q \leq 3e5
平面圖無割點、並且對應着至少一個AC自動機?
儘管不知道這條限制有個卵用。


正解

表示比賽的時候基本上沒有思考過。
這題暴力似乎還是可以寫的,判斷一個點在多邊形內部,可以將這個點和每個頂點連邊,極角排序,將排序後極角相鄰的邊之間的逆時針夾角求和。如果最終求和的結果爲2π2\pi,則在多邊形內。
或者也可以用射線法。

至於正解,這題有好多種做法:
先說神仙題解做法。
題解是建立一個匯點,放到最左邊,與最左邊的一個點連邊。
接下來以這個匯點爲根建一棵生成樹。
現在看成這樣一個模型:每個點上都帶着一個流量,這個流量沿着父親邊流出。最終所有的流量都流入匯點。
這樣對於每個點而言,FF=1F_出-F_入=1(出去的流量減進來的流量)
建出生成樹之後FF_出就是子樹大小。
對於一個多邊形而言,它內部的點也是FFF_出-F_入。道理可以如此理解:從外邊流入多邊形的流量,最終會出去;而起源於裏面的流量,會流出多邊形外。
結合平面圖的性質,這個東西看起來似乎挺顯然。但是不知道怎麼證。
於是計算貢獻的時候,枚舉每個點,如果出邊在多邊形外,那麼加上它的貢獻;如果入邊在多邊形外,就減去它的貢獻。將入邊極角排序,前綴和+二分就可以快速地入邊的貢獻。
於是時間複雜度就是O(nlgn)O(n \lg n)

然後是大佬們提供的做法:
一個是平面圖轉成對偶圖。原多邊形中對應的邊在對偶圖中刪掉,於是對偶圖中間會出現一個獨立出整體的連通塊。計算出這個連通塊的點數和邊數,用平面圖歐拉公式來計算出區域數。
於是這個問題就變成了:對於一個圖,支持詢問刪去一些邊之後,某個點所在連通塊中的點數和邊數。
預處理出每條邊出現的時間,線段樹分治+並查集隨便搞搞。
時間複雜度O(nlg2n)O(n \lg^2 n)
套用之前某毒瘤題的思路可以優化到O(nlgn)O(n \lg n)(線段樹上遞歸的時候將有關的節點建虛樹,其它的點縮起來)。

另一個是考慮射線法。首先爲了防止出題人卡,先給每個點隨機轉一個角度。
每個點向上做一條射線,如果穿過了多邊形的邊奇數次,它就在多邊形內。
假設多邊形是按照逆時針順序建的,這樣將從右邊往左邊的邊的貢獻記爲+1+1,左邊往右邊的邊的貢獻記爲1-1。於是一個點的貢獻就是射線穿過的邊的貢獻和。
離線,然後按照xx軸掃描線。用平衡樹按照縱座標的相對順序來記一下掃到的邊。
掃到一個點的時候,計算這個點對那些邊所屬的多邊形的貢獻。將貢獻掛在平衡樹上的記錄邊的節點上。二分出這個點能貢獻到的邊的區間,然後區間修改之。
時間複雜度O(nlgn)O(n \lg n)


代碼

代碼中有個關於判斷多邊形是逆時針還是順時針的問題。
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;
}
 

有一說一計算幾何真的不好打。


總結

面對這些抖機靈題,比賽時是很難想出來的,所以還是要靠數據結構的硬實力。
另外,關於計算幾何……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章