BZOJ4541[HNOI2016]矿区(对偶图)

题目链接

洛谷

BZOJ

前置知识

平面图转对偶图

简单理解“对偶图”就是,原图边把平面切成了很多块,对偶图中的点代表这些块(最外面的无穷域也可以算作一个块),相邻的块(即有公共边)之间连边,如下图(左边原图,右边对偶图):

1275607-20190225165811972-1578212825.png

不难发现原图中的每条边都对应了新图中的一条边

那么如何将一个平面图转成对偶图呢

先把无向边拆成两条有向边,这样每条边都可以被分配到一个块里

我们发现若一个点的入边\(i\)和出边\(j\)同属一个“块”,那么对这个点连出的边极角排序后,\(i\)的反向边一定和\(j\)相邻,那么我们可以借助极角排序先求出一个\(next\)数组,\(next[i]\)表示和边\(i\)同属一个块,且紧跟在\(i\)后的那条边

随后我们枚举每条未分配边,不断跳\(next\),把经过的边和这条边自身分配给一个新“块”,显然会有一个时刻跳回这条边自己,这时退出,继续枚举未分配边,直到把所有边分配

接下来枚举每条边,把它和它的反向边所分配的“块”连边即可

代码在最下方题解代码中

解析

先求对偶图

然后以无穷域为根求出一棵生成树,每个节点维护子树内面积和、面积的平方和

枚举所求区域的边界,若一个块在边界的父亲位置,减去儿子的贡献,若在儿子位置,加上儿子的贡献

结合画图比较好理解(其实是我不知道怎么讲比较清楚)

代码

PS.注意开long long,我因为P没开long long RE了半个下午。。。。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#define MAXN 200005
#define MAXM 1200005
#define sqr(x) ((x) * (x))

typedef long long LL;
const double eps = 1e-10;
struct Point {
    int x, y;
    Point(int _x = 0, int _y = 0):x(_x), y(_y) {};
} pt[MAXN];
struct Edge {
    int u, v, id;
    double ang;
    Edge() { memset(this, 0, sizeof(Edge)); }
    Edge(int _u, int _v, int _id) { u = _u, v = _v, id = _id, ang = atan2(pt[v].y - pt[u].y, pt[v].x - pt[u].x); }
    bool operator <(const Edge &e) const { return fabs(ang - e.ang) < eps ? v < e.v : ang < e.ang; }
} edge[MAXM];
std::vector<Edge> G[MAXN], tree[MAXM];
int N, M, Q, root, tot, cnt, next[MAXM], area[MAXM], qry[MAXM], fa[MAXM];
LL up[MAXM], s[MAXM], P;
char vis[MAXM], in_tree[MAXM];

char gc();
LL read();
void print(LL);
void build();
void dfs(int);
LL gcd(LL x, LL y) { return y ? gcd(y, x % y) : x; }
int main() {
    //freopen("1.in", "r", stdin);
    N = read(), M = read(), Q = read();
    for (int i = 1; i <= N; ++i) pt[i].x = read(), pt[i].y = read();
    for (int i = 1; i <= M; ++i) {
        int a = read(), b = read();
        edge[tot] = Edge(a, b, tot), G[a].push_back(edge[tot]), ++tot;
        edge[tot] = Edge(b, a, tot), G[b].push_back(edge[tot]), ++tot;
    }
    build();
    dfs(root);
    while (Q--) {
        int c = (read() + P) % N + 1;
        LL ans0 = 0, ans1 = 0;
        for (int i = 0; i < c; ++i) qry[i] = (read() + P) % N + 1;
        qry[c] = qry[0];
        for (int i = 1; i <= c; ++i) {
            Edge tmp = Edge(qry[i - 1], qry[i], 0);
            int t = (*std::lower_bound(G[qry[i - 1]].begin(), G[qry[i - 1]].end(), tmp)).id;
            if (!in_tree[t]) continue;
            if (fa[area[t]] == area[t ^ 1]) ans0 += up[area[t]], ans1 += s[area[t]];
            else ans0 -= up[area[t ^ 1]], ans1 -= s[area[t ^ 1]];
        }
        LL d = gcd(ans0, ans1);
        print(P = ans0 / d); putchar(' '); print(ans1 / d); putchar('\n');
    }
    return 0;
}
inline char gc() {
    static char buf[1000000], *p1, *p2;
    if (p1 == p2) p1 = (p2 = buf) + fread(buf, 1, 1000000, stdin);
    return p1 == p2 ? EOF : *p2++;
}
inline LL read() {
    LL res = 0, op; char ch = gc();
    while (ch != '-' && (ch < '0' || ch > '9')) ch = gc();
    op = (ch == '-' ? ch = gc(), -1 : 1);
    while (ch >= '0' && ch <= '9') res = (res << 1) + (res << 3) + ch - '0', ch = gc();
    return res * op;
}
inline void print(LL x) {
    static int buf[30];
    if (!x) putchar('0');
    else {
        if (x < 0) putchar('-'), x = -x;
        while (x) buf[++buf[0]] = x % 10, x /= 10;
        while (buf[0]) putchar('0' + buf[buf[0]--]);
    }
}
inline LL get_square(int i, int j, int k) {
    LL x1 = pt[j].x - pt[i].x, x2 = pt[k].x - pt[i].x;
    LL y1 = pt[j].y - pt[i].y, y2 = pt[k].y - pt[i].y;
    return x1 * y2 - x2 * y1;
}
void build() {
    for (int i = 1; i <= N; ++i) std::sort(G[i].begin(), G[i].end());
    for (int i = 0; i < tot; ++i) {
        int v = edge[i].v;
        std::vector<Edge>::iterator p = std::lower_bound(G[v].begin(), G[v].end(), edge[i ^ 1]);
        if (p == G[v].begin()) p = G[v].end();
        --p, next[i] = p->id;
    }
    for (int i = 0; i < tot; ++i) {
        if (area[i]) continue;
        area[i] = area[next[i]] = ++cnt;
        for (int j = next[i]; edge[j].v ^ edge[i].u; j = next[j], area[j] = cnt)
            s[cnt] += get_square(edge[i].u, edge[j].u, edge[j].v);
        if (s[cnt] <= 0) root = cnt;
    }
    for (int i = 0; i < tot; ++i)
        tree[area[i]].push_back(Edge(area[i], area[i ^ 1], i));
}
void dfs(int u) {
    up[u] = sqr(s[u]), s[u] <<= 1, vis[u] = 1;
    for (int i = 0; i < tree[u].size(); ++i) {
        Edge e = tree[u][i];
        if (!vis[e.v]) {
            in_tree[e.id] = in_tree[e.id ^ 1] = 1, fa[e.v] = u;
            dfs(e.v);
            s[u] += s[e.v], up[u] += up[e.v];
        }
    }
}

转载于:https://www.cnblogs.com/Rhein-E/p/10431863.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值