平面图转对偶图专题
平面图转对偶图
平面图:图且任意两条边不相交
对偶图:将平面图的面抠出来形成的图,注意平面图之外的还有一个面。通过将无向边拆成两条有向边进行完成。
具体来说,拆边之后,考虑用一个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;
}