題目
給出一個無重邊、自環的平面圖。需要回答若干個詢問,每次詢問給出一個簡單環,求這個簡單環形成的多邊形內部有多少個點(包括邊界上)。
平面圖無割點、並且對應着至少一個AC自動機?
儘管不知道這條限制有個卵用。
正解
表示比賽的時候基本上沒有思考過。
這題暴力似乎還是可以寫的,判斷一個點在多邊形內部,可以將這個點和每個頂點連邊,極角排序,將排序後極角相鄰的邊之間的逆時針夾角求和。如果最終求和的結果爲,則在多邊形內。
或者也可以用射線法。
至於正解,這題有好多種做法:
先說神仙題解做法。
題解是建立一個匯點,放到最左邊,與最左邊的一個點連邊。
接下來以這個匯點爲根建一棵生成樹。
現在看成這樣一個模型:每個點上都帶着一個流量,這個流量沿着父親邊流出。最終所有的流量都流入匯點。
這樣對於每個點而言,(出去的流量減進來的流量)
建出生成樹之後就是子樹大小。
對於一個多邊形而言,它內部的點也是。道理可以如此理解:從外邊流入多邊形的流量,最終會出去;而起源於裏面的流量,會流出多邊形外。
結合平面圖的性質,這個東西看起來似乎挺顯然。但是不知道怎麼證。
於是計算貢獻的時候,枚舉每個點,如果出邊在多邊形外,那麼加上它的貢獻;如果入邊在多邊形外,就減去它的貢獻。將入邊極角排序,前綴和+二分就可以快速地入邊的貢獻。
於是時間複雜度就是
然後是大佬們提供的做法:
一個是平面圖轉成對偶圖。原多邊形中對應的邊在對偶圖中刪掉,於是對偶圖中間會出現一個獨立出整體的連通塊。計算出這個連通塊的點數和邊數,用平面圖歐拉公式來計算出區域數。
於是這個問題就變成了:對於一個圖,支持詢問刪去一些邊之後,某個點所在連通塊中的點數和邊數。
預處理出每條邊出現的時間,線段樹分治+並查集隨便搞搞。
時間複雜度
套用之前某毒瘤題的思路可以優化到(線段樹上遞歸的時候將有關的節點建虛樹,其它的點縮起來)。
另一個是考慮射線法。首先爲了防止出題人卡,先給每個點隨機轉一個角度。
每個點向上做一條射線,如果穿過了多邊形的邊奇數次,它就在多邊形內。
假設多邊形是按照逆時針順序建的,這樣將從右邊往左邊的邊的貢獻記爲,左邊往右邊的邊的貢獻記爲。於是一個點的貢獻就是射線穿過的邊的貢獻和。
離線,然後按照軸掃描線。用平衡樹按照縱座標的相對順序來記一下掃到的邊。
掃到一個點的時候,計算這個點對那些邊所屬的多邊形的貢獻。將貢獻掛在平衡樹上的記錄邊的節點上。二分出這個點能貢獻到的邊的區間,然後區間修改之。
時間複雜度
代碼
代碼中有個關於判斷多邊形是逆時針還是順時針的問題。
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;
}
有一說一計算幾何真的不好打。
總結
面對這些抖機靈題,比賽時是很難想出來的,所以還是要靠數據結構的硬實力。
另外,關於計算幾何……