洛谷P3249/bzoj4541/loj2052 矿区 平面图转对偶图

题目分析

所谓对偶图,就是将平面图中所有的面变成点,点变成面,边“旋转90度”后得到的图。

如何转对偶图,关键就是如何划分原图中的面,这个方法是,双向边先看成两条单向边,这样每条边都属于一个面,将以每一个点为起点的边极角排序,对于一条边(s,t),我们在以t为起点的边中找到(t,s),排序后其上一条边就是当前面的下一条边界,这样一直找到整个区域闭合,就说明把这个面上的边全部找出来了。这个步骤可以利用STL中的vector轻松做到。

每条边连接两个面(即它所在的面和它的反向边所在的面),便建好了对偶图。在这个对偶图中随意地拿出一棵生成树,以无界域(即原图中外围无限的面)为根。如何找到无界域呢?利用叉积算有向面积的时候,算出来是负数的就是无界域。

然后标记所有树边,记录生成树上每棵子树中的矿区面积和及面积平方和。

对于一个询问,先找到这个询问里出现的边,如果是非树边就忽略,否则如果这条边所在的面是儿子,就加上子树的面积,是父亲,就减去儿子子树的面积。这个画画图可以感受一下。

代码

#include
using namespace std;
int read() {
    int q=0,w=1;char ch=' ';
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q*w;
}
typedef long long LL;
typedef double db;
const int N=200005,M=1200005;const db eps=1e-10;
#define RI register int
int n,m,Q,tot=1,cnt,rt;LL ans1,ans2;
struct point{int x,y;}p[N];
point operator - (point a,point b) {return (point){a.x-b.x,a.y-b.y};}
LL operator * (point a,point b) {return 1LL*a.x*b.y-1LL*a.y*b.x;}
struct edge{int id,u,v;db jd;}e[M];
bool operator < (edge a,edge b) {return fabs(a.jd-b.jd)int nxt[M],pos[M],f[M],vis[M],istr[M],ask[M];LL s[M],ss[M];
vector h[N],tr[M];

void link(int x,int y) {
    ++tot,e[tot]=(edge){tot,x,y,atan2(p[y].y-p[x].y,p[y].x-p[x].x)};
    h[x].push_back(e[tot]);
}
void build() {
    for(RI i=1;i<=n;++i) sort(h[i].begin(),h[i].end());
    for(RI i=2;i<=tot;++i) {
        int v=e[i].v;
        vector::iterator kl=lower_bound(h[v].begin(),h[v].end(),e[i^1]);
        if(kl==h[v].begin()) kl=h[v].end();//其前一条就是最后一条
        --kl,nxt[i]=(*kl).id;
    }
    for(RI i=2;i<=tot;++i) {
        if(pos[i]) continue;
        pos[i]=pos[nxt[i]]=++cnt;
        for(RI j=nxt[i];e[j].v!=e[i].u;j=nxt[j],pos[j]=cnt)
            s[cnt]+=(p[e[j].u]-p[e[i].u])*(p[e[j].v]-p[e[i].u]);//计算面积
        if(s[cnt]<=0) rt=cnt;//无穷域
    }
    for(RI i=2;i<=tot;++i) tr[pos[i]].push_back((edge){i,pos[i],pos[i^1]});
}
void dfs(int x,int las) {//生成树
    f[x]=las,ss[x]=1LL*s[x]*s[x],s[x]<<=1,vis[x]=1;
    //叉积算面积后应该除以2,但是为了避免小数,所以分子分母同时乘4
    for(RI i=0,sz=tr[x].size();iint v=tr[x][i].v;
        if(vis[v]) continue;
        istr[tr[x][i].id]=istr[tr[x][i].id^1]=1,dfs(v,x);
        s[x]+=s[v],ss[x]+=ss[v];
    }
}

LL gcd(LL a,LL b) {return b?gcd(b,a%b):a;}
void work() {
    while(Q--) {
        int js=(read()+ans1)%n+1;
        for(RI i=1;i<=js;++i) ask[i]=(read()+ans1)%n+1;
        ask[js+1]=ask[1],ans1=ans2=0;
        for(RI i=1;i<=js;++i) {
            int x=ask[i],y=ask[i+1];
            edge ke=(edge){0,x,y,atan2(p[y].y-p[x].y,p[y].x-p[x].x)};
            vector::iterator kl=lower_bound(h[x].begin(),h[x].end(),ke);//找边
            int j=(*kl).id;
            if(!istr[j]) continue;//该边所在区域,是儿子就加是父亲就减
            if(f[pos[j]]==pos[j^1]) ans1+=ss[pos[j]],ans2+=s[pos[j]];
            else ans1-=ss[pos[j^1]],ans2-=s[pos[j^1]];
        }
        LL tmp=gcd(ans1,ans2);
        ans1/=tmp,ans2/=tmp;
        printf("%lld %lld\n",ans1,ans2);
    }
}

int main()
{
    int x,y;
    n=read(),m=read(),Q=read();
    for(RI i=1;i<=n;++i) p[i]=(point){read(),read()};
    for(RI i=1;i<=m;++i) x=read(),y=read(),link(x,y),link(y,x);
    build(),dfs(rt,0),work();
    return 0;
}

你可能感兴趣的:(计算几何)