平面图转对偶图专题

平面图转对偶图

平面图:图且任意两条边不相交
对偶图:将平面图的面抠出来形成的图,注意平面图之外的还有一个面。通过将无向边拆成两条有向边进行完成。
具体来说,拆边之后,考虑用一个vector存下从该点出发的所有边,进行极角排序。然后对每条边在其终点的vector中进行二分找到第一个极角小于等于反边极角的设为\(nxt_i\)。然后找多边形的时候不停地跳\(nxt_i\)直到形成一个闭环。找外面的面的话可以通过计算面积,面积非正的话就是外面的面。

//模板
#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
#define PII pair<int, int>
#define FOR(i, l, r) for (int i = (l); i <= (r); ++i)
#define ROF(i, r, l) for (int i = (r); i >= (l); --i)
#define FI first
#define SE second
#define SZ size()
using namespace std;
typedef long long ll;
const int M = 1.2e6 + 5;
const int N = 1.2e6 + 5;
const double eps = 1e-10;
int n, m;
ll s[N];
struct pt {
    int x, y;
    pt() {}
    pt(int _x, int _y) {
        x = _x;
        y = _y;
    }
    pt operator - (const pt &t) const {
        return pt(x - t.x, y - t.y);
    }
    ll operator * (const pt &t) const {
        return 1LL * x * t.y - 1LL * y * t.x;
    }
} p[N];
struct edge {
    int id, u, v;
    double at;
    edge() {}
    edge(int _id, int _u, int _v, double _at) {
        id = _id;
        u = _u;
        v = _v;
        at = _at;
    }
    bool operator < (const edge &t) const {
        return fabs(at - t.at) < eps ? v < t.v : at < t.at;
    }
} e[N];
vector<edge> st[N];
int ed[N][2], pos[N], cnt, tot = 1, nxt[N], rt;
void add(int u, int v) {
    ++tot;
    e[tot] = edge(tot, u, v, atan2(p[v].y - p[u].y, p[v].x - p[u].x));
    st[u].PB(e[tot]);
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    FOR (i, 1, n) {
        cin >> p[i].x >> p[i].y;
    }
    FOR (i, 1, m) {
        cin >> ed[i][0] >> ed[i][1];
        add(ed[i][0], ed[i][1]);
        add(ed[i][1], ed[i][0]);
    }
    FOR (i, 1, n) {
        sort(st[i].begin(), st[i].end());
    }
    FOR (i, 2, tot) {
        int v = e[i].v;
        vector<edge>::iterator p = lower_bound(st[v].begin(), st[v].end(), e[i ^ 1]);
        if (p == st[v].begin()) {
            p = st[v].end();
        }
        --p;
        nxt[i] = (*p).id;
    }
    FOR (i, 2, tot) {
        if (pos[i]) {
            continue;
        }
        pos[i] = pos[nxt[i]] = ++cnt;
        int j = nxt[i];
        while (e[j].v != e[i].u) {
            s[cnt] += (p[e[j].u] - p[e[i].u]) * (p[e[j].v] - p[e[i].u]);
            pos[j = nxt[j]] = cnt;
        }
        if (s[cnt] <= 0) {
            rt = cnt;
        }
    }
    return 0;
}

一些例题

P4001 [ICPC-Beijing 2006] 狼抓兔子

简单的平面图转对偶图优化网络流。首先发现原问题是最小割,然后转化成路径,发现一定是右上方走到左下方,其代价就是经过的边的价值和。直接转对偶图建边就变成了最短路。简单题。

#include <bits/stdc++.h>
#define int long long
#define PB push_back
#define MP make_pair
#define PII pair<int, int>
#define FOR(i, l, r) for (int i = (l); i <= (r); ++i)
#define ROF(i, r, l) for (int i = (r); i >= (l); --i)
#define FI first
#define SE second
#define SZ size()
using namespace std;
const int N = 2e6 + 5;
const int mod = 998244353;
const int inf = 1e18;
int n, m, S, T, dis[N];
vector<PII> G[N];
inline int id(int x, int y, bool o) {
    if (!x || y >= m) {
        return S;
    }
    if (!y || x >= n) {
        return T;
    }
    return (x - 1) * m * 2 + (y - 1) * 2 + o;
}
struct node {
    int u, d;
    node() {}
    node(int _u, int _d) {
        u = _u;
        d = _d;
    }
    bool operator < (const node &t) const {
        return d > t.d;
    }
};
bool vis[N];
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    S = n * m * 2 + 1;
    T = S + 1;
    FOR (i, 1, n) {
        FOR (j, 1, m - 1) {
            int val;
            cin >> val;
            G[id(i - 1, j, 0)].PB(MP(id(i, j, 1), val));
            G[id(i, j, 1)].PB(MP(id(i - 1, j, 0), val));
        }
    }
    FOR (i, 1, n - 1) {
        FOR (j, 1, m) {
            int val;
            cin >> val;
            G[id(i, j - 1, 1)].PB(MP(id(i, j, 0), val));
            G[id(i, j, 0)].PB(MP(id(i, j - 1, 1), val));
        }
    }
    FOR (i, 1, n - 1) {
        FOR (j, 1, m - 1) {
            int val;
            cin >> val;
            G[id(i, j, 1)].PB(MP(id(i, j, 0), val));
            G[id(i, j, 0)].PB(MP(id(i, j, 1), val));
        }
    }
    memset(dis, 0x3f, sizeof(dis));
    dis[S] = 0;
    priority_queue<node> q;
    q.push(node(S, 0));
    while (!q.empty()) {
        int u = q.top().u;
        q.pop();
        if (vis[u]) {
            continue;
        }
        vis[u] = 1;
        for (auto [v, w]: G[u]) {
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                q.push(node(v, dis[v]));
            }
        }
    }
    cout << dis[T] << '\n';
    return 0;
}

P3249 [HNOI2016] 矿区

进阶题。套路地平面图转对偶图,建图。发现问题转化成一坨连通块求面积和以及面积平方和。建出搜索树统计子树的答案。考虑忽略非树边,对于树边若出发点是儿子则加,否则减去儿子的贡献。发现这样竟然是对的。

#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
#define PII pair<int, int>
#define FOR(i, l, r) for (int i = (l); i <= (r); ++i)
#define ROF(i, r, l) for (int i = (r); i >= (l); --i)
#define FI first
#define SE second
#define SZ size()
using namespace std;
typedef long long ll;
const int M = 1.2e6 + 5;
const int N = 1.2e6 + 5;
const double eps = 1e-10;
int n, m, q, poi[M];
ll s[N], s2[N];
bool vis[N];
vector<PII> G[N];
struct pt {
    int x, y;
    pt() {}
    pt(int _x, int _y) {
        x = _x;
        y = _y;
    }
    pt operator - (const pt &t) const {
        return pt(x - t.x, y - t.y);
    }
    ll operator * (const pt &t) const {
        return 1LL * x * t.y - 1LL * y * t.x;
    }
} p[N];
struct edge {
    int id, u, v;
    double at;
    edge() {}
    edge(int _id, int _u, int _v, double _at) {
        id = _id;
        u = _u;
        v = _v;
        at = _at;
    }
    bool operator < (const edge &t) const {
        return fabs(at - t.at) < eps ? v < t.v : at < t.at;
    }
} e[N];
vector<edge> st[N];
int ed[N][2], pos[N], cnt, tot = 1, nxt[N], rt, fa[N];
bool flag[N];
void add(int u, int v) {
    ++tot;
    e[tot] = edge(tot, u, v, atan2(p[v].y - p[u].y, p[v].x - p[u].x));
    st[u].PB(e[tot]);
}
void dfs(int u, int f) {
    s2[u] = s[u] * s[u];
    s[u] <<= 1;
    fa[u] = f;
    vis[u] = 1;
    for (auto [v, w]: G[u]) {
        if (vis[v]) {
            continue;
        } 
        flag[w] = flag[w ^ 1] = 1;
        dfs(v, u);
        s[u] += s[v];
        s2[u] += s2[v];
    }
}
ll ans1, ans2;
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m >> q;
    FOR (i, 1, n) {
        cin >> p[i].x >> p[i].y;
    }
    FOR (i, 1, m) {
        cin >> ed[i][0] >> ed[i][1];
        add(ed[i][0], ed[i][1]);
        add(ed[i][1], ed[i][0]);
    }
    FOR (i, 1, n) {
        sort(st[i].begin(), st[i].end());
    }
    FOR (i, 2, tot) {
        int v = e[i].v;
        vector<edge>::iterator p = lower_bound(st[v].begin(), st[v].end(), e[i ^ 1]);
        if (p == st[v].begin()) {
            p = st[v].end();
        }
        --p;
        nxt[i] = (*p).id;
    }
    FOR (i, 2, tot) {
        if (pos[i]) {
            continue;
        }
        pos[i] = pos[nxt[i]] = ++cnt;
        int j = nxt[i];
        while (e[j].v != e[i].u) {
            s[cnt] += (p[e[j].u] - p[e[i].u]) * (p[e[j].v] - p[e[i].u]);
            pos[j = nxt[j]] = cnt;
        }
        if (s[cnt] <= 0) {
            rt = cnt;
        }
    }
    FOR (i, 2, tot) {
        G[pos[i]].PB(MP(pos[i ^ 1], i));
    }
    dfs(rt, -1);
    while (q --> 0) {
        int x;
        cin >> x;
        x = (x + ans1) % n + 1;
        FOR (i, 1, x) {
            cin >> poi[i];
            poi[i] = (poi[i] + ans1) % n + 1;            
        }
        poi[x + 1] = poi[1];
        ans1 = ans2 = 0;
        FOR (i, 1, x) {
            int u = poi[i], v = poi[i + 1];
            edge t = edge(0, u, v, atan2(p[v].y - p[u].y, p[v].x - p[u].x));
            vector<edge>::iterator p = lower_bound(st[u].begin(), st[u].end(), t);
            int id = (*p).id;
            if (!flag[id]) {
                continue;
            }
            if (fa[pos[id]] == pos[id ^ 1]) {
                ans1 += s2[pos[id]];
                ans2 += s[pos[id]];
            } else {
                ans1 -= s2[pos[id ^ 1]];
                ans2 -= s[pos[id ^ 1]];
            }
        }
        ll g = __gcd(ans1, ans2);
        ans1 /= g;
        ans2 /= g;
        cout << ans1 << ' ' << ans2 << '\n';
    }
    return 0;
}

P4073 [WC2013] 平面图

平面图转对偶图后发现只要定位了某个点再哪个平面就能通过\(kruscal\)重构树算出答案。关键在于定位。利用题目中的性质,由于题目说了任意两条边不相交,那么两条边开头的大小关系与再任意一个点的大小关系一定相同。那么这样的话考虑扫描线,再起始点加入这条边,在终点删除。用一个set维护当前所有边,两条线的大小关系只要比较开头的大小即可。

#include <bits/stdc++.h>
#define PB push_back
#define MP make_pair
#define PII pair<int, int>
#define FOR(i, l, r) for (int i = (l); i <= (r); ++i)
#define ROF(i, r, l) for (int i = (r); i >= (l); --i)
#define FI first
#define SE second
#define SZ size()
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
const double eps = 1e-12;
int n, m, pos[N], q;
struct point {
    int x, y;
    point() {}
    point(int _x, int _y) {
        x = _x;
        y = _y;
    }
    bool operator == (const point &t) const {
        return x == t.x && y == t.y;
    }
} p[N];
//点
struct Vector {
    int x, y;
    Vector() {}
    Vector(int _x, int _y) {
        x = _x;
        y = _y;
    }
    Vector(point _x, point _y) {
        y = _y.y - _x.y;
        x = _y.x - _x.x;
    }
    Vector operator - (const Vector &t) const {
        return Vector(x - t.x, y - t.y);
    }
    ll operator * (const Vector &t) const {
        return 1LL * x * t.y - 1LL * y * t.x;
    }
};
//向量
struct line {
    point st;
    double k;
    line() {}
    line(point x, point y) {
        st = x;
        k = 1.0 * (y.y - x.y) / (y.x - x.x);
    }
    line(point x, double _k) {
        st = x;
        k = _k;
    }
    bool operator < (const line &t) const {
        if (st == t.st) {
            return k < t.k;
        }
        int x = max(st.x, t.st.x);
        return st.y + (x - st.x) * k < t.st.y + (x - t.st.x) * t.k;
        //由于保证两条直线不交所以可以直接比较同x下的y值大小,非常巧妙
    }
    bool operator == (const line &t) const {
        return st == t.st && fabs(k - t.k) < eps;
    }
};
//直线二分
struct edge {
    int id, u, v, w;
    double at;
    edge() {}
    edge(int _id, int _u, int _v, int _w, double _at) {
        id = _id;
        u = _u;
        v = _v;
        w = _w;
        at = _at;
    }
    bool operator < (const edge &t) const {
        return fabs(at - t.at) < eps ? v < t.v : at < t.at;
    }
} e[N];
//极角排序
struct K_edge {
    int u, v, w;
    K_edge() {}
    K_edge(int _u, int _v, int _w) {
        u = _u;
        v = _v;
        w = _w;
    }
    bool operator < (const K_edge &t) const {
        return w < t.w;
    }
} ke[N];
//Kruscal重构树
struct scan {
    int ty, pos, id;
    line l;
    scan() {}
    scan(int _t, int _pos, int _id, line _l) {
        ty = _t;
        id = _id;
        l = _l;
        pos = _pos;
    }
    bool operator < (const scan &t) const {
        return pos == t.pos ? ty < t.ty : pos < t.pos;
    }
} sc[N << 1];
//扫描线
vector<edge> h[N];
vector<int> G[N];
int tot = -1, cnt, nxt[N], forb, kcnt, f[N], fa[N][20], val[N], L[N], R[N], timer, ans[N], stot;
set<pair<line, int>> Set;
bool flag[N];
void add(int u, int v, int w) {
    ++tot;
    e[tot] = edge(tot, u, v, w, atan2(p[v].y - p[u].y, p[v].x - p[u].x));
    h[u].PB(e[tot]);
}
int find(int x) {
    return f[x] == x ? x : f[x] = find(f[x]);
}
void dfs(int u, int ft) {
    L[u] = ++timer;
    fa[u][0] = ft;
    FOR (i, 1, 19) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    for (int v: G[u]) {
        if (v == ft) {
            continue;
        }
        dfs(v, u);
    }
    R[u] = timer;
}
bool up(int u, int v) {
    return !u || (L[u] <= L[v] && R[v] <= R[u]);
}
int lca(int u, int v) {
    if (up(u, v)) {
        return u;
    }
    if (up(v, u)) {
        return v;
    }
    ROF (i, 19, 0)
        if (!up(fa[u][i], v)) 
            u = fa[u][i];
    return fa[u][0];
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;
    FOR (i, 1, n) {
        cin >> p[i].x >> p[i].y;
        p[i].x <<= 1;
        p[i].y <<= 1;
    }
    FOR (i, 1, m) {
        int u, v, h;
        cin >> u >> v >> h;
        add(u, v, h);
        add(v, u, h);
    }
    //平面图转对偶图
    FOR (i, 1, n) {
        sort(h[i].begin(), h[i].end());
    }
    FOR (i, 0, tot) {
        int v = e[i].v;
        vector<edge>::iterator it = lower_bound(h[v].begin(), h[v].end(), e[i ^ 1]);
        if (it == h[v].begin()) {
            it = h[v].end();
        }
        --it;
        nxt[i] = (*it).id;
    }
    FOR (i, 0, tot) {
        if (pos[i]) {
            continue;
        }
        pos[i] = pos[nxt[i]] = ++cnt;
        int j = nxt[i];
        ll s = 0;
        while (e[j].v != e[i].u) {
            s += Vector(p[e[j].u], p[e[i].u]) * Vector(p[e[j].v], p[e[i].u]);
            pos[j = nxt[j]] = cnt;
        }
        if (s <= 0) {
            forb = cnt;
        }
    }
    //建树
    FOR (i, 0, tot) {
        if ((i & 1) || pos[i] == forb || pos[i ^ 1] == forb) {
            continue;
        }
        ke[++kcnt] = K_edge(pos[i], pos[i ^ 1], e[i].w);
    }
    sort(ke + 1, ke + kcnt + 1);
    FOR (i, 1, cnt) {
        f[i] = i;
    }
    int all = cnt;
    FOR (i, 1, kcnt) {
        int x = find(ke[i].u), y = find(ke[i].v);
        if (x == y) {
            continue;
        }
        ++all;
        f[x] = all;
        f[y] = all;
        f[all] = all;
        fa[x][0] = fa[y][0] = all;
        val[all] = ke[i].w;
        G[all].PB(x);
        G[all].PB(y);
    }
    dfs(all, 0);
    FOR (i, 0, tot) {
        if (p[e[i].u].x < p[e[i].v].x) {
            sc[++stot] = scan(2, p[e[i].u].x, pos[i ^ 1], line(p[e[i].u], p[e[i].v]));
            sc[++stot] = scan(1, p[e[i].v].x, pos[i ^ 1], line(p[e[i].u], p[e[i].v]));
        }
    }
    cin >> q;
    FOR (i, 1, q) {
        double ax, bx, ay, by;
        cin >> ax >> ay >> bx >> by;
        ax *= 2;
        ay *= 2;
        bx *= 2;
        by *= 2;
        sc[++stot] = scan(3, ax, i, line(point(ax, ay), 0.0));
        sc[++stot] = scan(3, bx, i, line(point(bx, by), 0.0));
    }
    sort(sc + 1, sc + stot + 1);
    FOR (i, 1, stot) {
        if (sc[i].ty == 2) {
            Set.insert(MP(sc[i].l, sc[i].id));
        } else if (sc[i].ty == 1) {
            Set.erase(MP(sc[i].l, sc[i].id));
        } else {
            if (flag[sc[i].id]) {
                continue;
            }
            set<pair<line, int>>::iterator it = Set.upper_bound(MP(sc[i].l, 0));
            if (it == Set.end() || it == Set.begin()) {
                flag[sc[i].id] = 1;
                continue;
            }
            if (!ans[sc[i].id]) {
                ans[sc[i].id] = (*it).SE;
            } else {
                ans[sc[i].id] = val[lca((*it).SE, ans[sc[i].id])];
            }
        }
    }
    FOR (i, 1, q) {
        if (flag[i]) {
            cout << -1 << '\n';
        } else {
            cout << ans[i] << '\n';
        }
    }
    return 0;
}

posted on 2024-08-27 10:25  liyelin  阅读(7)  评论(0编辑  收藏  举报