1 条题解

  • 0
    @ 2025-8-24 21:46:57

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar litble
    苟……苟活者在淡红的血色中,会依稀看见微茫的希望(AFO)

    搬运于2025-08-24 21:46:57,当前版本为作者最后更新于2018-03-28 11:31:49,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    平面图转对偶图

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

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

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

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

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

    #include<bits/stdc++.h>
    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)<eps?a.v<b.v:a.jd<b.jd;}
    int nxt[M],pos[M],f[M],vis[M],istr[M],ask[M];LL s[M],ss[M];
    vector<edge> 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<edge>::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();i<sz;++i) {
            int 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<edge>::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;
    }
    
    • 1

    信息

    ID
    2322
    时间
    3000ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者