[BZOJ4541] [HNOI2016] 矿区

题目链接

BZOJ:https://lydsy.com/JudgeOnline/problem.php?id=4541.

LOJ:https://loj.ac/problem/2052.

洛谷:https://www.luogu.org/problemnew/show/P3249.

Solution

平面图转对偶图。

平面图转对偶图大概就是说,对于一个平面图,它把平面分割成了若干个区块,对于每个区块抽象成一个点,原图上的分割两个区块的边转化成连接两个点的边,转化完之后的图就叫对偶图。

对于这道题,我们先把对偶图弄出来,随便整一棵生成树,对于每个点我们记录当前区块的面积的平方。

然后对于询问我们逆时针扫描这个图,然后对经过的边分析,如果当前边为非树边就不管,否则我们可以随便定义一个正方向,如果当前边是儿子指向父亲则ans+=s[son]\(s[x]\)表示\(x\)子树的点权和,否则ans-=s[son]

你画个图就能明白了,具体的我也不会证

具体见代码

#include<bits/stdc++.h>
using namespace std;

void read(int &x) {
    x=0;int f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}

void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}

#define lf double
#define ll long long 

const int maxn = 2e6+10;
const int inf = 1e9;
const lf eps = 1e-8;

struct P {
    int x,y;
    P operator - (const P &rhs) const {return (P){x-rhs.x,y-rhs.y};}
    ll operator * (const P &rhs) const {return 1ll*x*rhs.y-1ll*y*rhs.x;}
}p[maxn];

ll gcd(ll a,ll b) {return !b?a:gcd(b,a%b);}

struct edge{
    int u,v,id;lf ang;
    edge() {}
    edge(int _u,int _v) {u=_u,v=_v,ang=atan2(p[v].y-p[u].y,p[v].x-p[u].x);}
    bool operator < (const edge &rhs) const {return ang<rhs.ang;}
}a[maxn];

vector <edge > e[maxn];

int n,m,Q,nxt[maxn],tot=1,bel[maxn],cnt,rt,f[maxn],in[maxn],tr[maxn];
ll s2[maxn],ans,val[maxn];

int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}

void ins(int u,int v) {a[++tot]=edge(u,v),a[tot].id=tot;e[u].push_back(a[tot]);}

struct Tree {
    int head[maxn],tot,fa[maxn];
    struct Edge{int to,nxt;}e[maxn<<1];

    void add(int u,int v) {e[++tot]=(Edge){v,head[u]},head[u]=tot;}
    void ins(int u,int v) {add(u,v),add(v,u);}
    
    void dfs(int x,int F) {
        fa[x]=F,s2[x]=val[x];
        for(int i=head[x];i;i=e[i].nxt)
            if(e[i].to!=fa[x]) dfs(e[i].to,x),s2[x]+=s2[e[i].to];
    }
}T;

void prepare() {
    for(int i=1;i<=n;i++) sort(e[i].begin(),e[i].end());  //极角排序
    for(int i=2;i<=tot;i++) {   //nxt[i]表示当前走到第i条边,下一条边走哪条
        int v=a[i].v;
        int t=lower_bound(e[v].begin(),e[v].end(),edge(a[i].v,a[i].u))-e[v].begin()-1;
        if(t==-1) t=e[v].size()-1;
        nxt[i]=e[v][t].id;
    }
    for(int i=2;i<=tot;i++) {
        if(bel[i]) continue;
        int now=nxt[i];ll s=0;bel[i]=bel[now]=++cnt;
        s+=(p[a[now].u]-p[a[i].u])*(p[a[now].v]-p[a[i].u]);
        while(a[now].v!=a[i].u) {
            now=nxt[now];
            bel[now]=cnt;s+=(p[a[now].u]-p[a[i].u])*(p[a[now].v]-p[a[i].u]);
        }if(s<=0) rt=cnt;
        val[cnt]=s*s; //绕着多边形转一圈,当前多边形标号为cnt
    }
    for(int i=1;i<=cnt;i++) f[i]=i;
    for(int i=2;i<=tot;i+=2) {
        int u=find(bel[i]),v=find(bel[i^1]);
        if(u!=v) f[u]=v,T.ins(bel[i],bel[i^1]),tr[i]=tr[i^1]=1;
    }
    T.dfs(rt,0);
}

void solve() {
    int k;read(k);k=(1ll*k+ans)%n+1;
    for(int i=1;i<=k;i++) read(in[i]),in[i]=(1ll*in[i]+ans)%n+1;in[k+1]=in[1];
    ans=0;ll res=0;
    for(int i=1;i<=k;i++) {
        int r=in[i],t=lower_bound(e[r].begin(),e[r].end(),edge(in[i],in[i+1]))-e[r].begin();
        res+=(p[r]-p[in[1]])*(p[in[i+1]]-p[in[1]]);
        int id=e[r][t].id;
        if(!tr[id]) continue;
        if(bel[id]==T.fa[bel[id^1]]) ans-=s2[bel[id^1]];
        else if(T.fa[bel[id]]==bel[id^1]) ans+=s2[bel[id]];
    }ans=abs(ans),res=abs(res);res<<=1;
    ll t=gcd(ans,res);ans/=t,res/=t;
    printf("%lld %lld\n",ans,res);
}

int main() {
    read(n),read(m),read(Q);
    for(int i=1;i<=n;i++) read(p[i].x),read(p[i].y);
    for(int i=1,x,y;i<=m;i++) read(x),read(y),ins(x,y),ins(y,x);
    prepare();
    for(int i=1;i<=Q;i++) solve();
    return 0;
}

转载于:https://www.cnblogs.com/hbyer/p/10636773.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值