2025.5.24 图论杂题
水哥选的杂题,有些题十分困难.
Luogu P10665 [AMPPZ2013] Bytehattan
对偶图板子题.
定义平面图为不存在两边相交的图,这个图将平面分割为了很多部分,每个部分称为这个平面图的一个面.
现在就可以定义对偶图:平面图的每个面作为节点,彼此相连构成的图.
回到原题,容易看出网格图就是平面图. 考虑 \(n\times n\) 的网格图其对偶图初始为 \((n-1)(n-1)+1\) 个孤立的点,维护原图的对偶图,割边操作等价于在对偶图上对应两点连边.
一个重要的观察是原图两点不连通当且仅当两点被对偶图分隔开,也就是在连边之前这两点对应面在对偶图上已经连通,连边就会使对偶图形成一个环隔开原图的点.
所以原题就变成了一个在线连边,查询连通性的问题,用并查集可以简单维护.
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1.5e2 + 10, maxm = 2.25e6 + 10;
int n, k, u, v, out; bool lst = true;
char op1, op2;
int fa[maxm];
inline int find(int i) {return (i == fa[i]) ? i : fa[i] = find(fa[i]);}
inline bool merge() {if((u = find(u)) == (v = find(v))) return false; fa[u] = v; return true;}
int main() {
ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> k; out = (n - 1) * (n - 1) + 1; for(int i = 1; i <= out; i++) fa[i] = i;
for(int i = 1, a1, b1, a2, b2; i <= k; i++) {
cin >> a1 >> b1 >> op1 >> a2 >> b2 >> op2;
if(lst) {
u = (a1 == n || b1 == n) ? out : (a1 - 1) * (n - 1) + b1;
v = (op1 == 'N') ? ((a1 == 1) ? out : (a1 - 2) * (n - 1) + b1) : (b1 == 1) ? out : (a1 - 1) * (n - 1) + b1 - 1;
}
else {
u = (a2 == n || b2 == n) ? out : (a2 - 1) * (n - 1) + b2;
v = (op2 == 'N') ? ((a2 == 1) ? out : (a2 - 2) * (n - 1) + b2) : (b2 == 1) ? out : (a2 - 1) * (n - 1) + b2 - 1;
}
cout << ((lst = merge()) ? "TAK\n" : "NIE\n");
}
return 0;
}
Luogu P9697 [GDCPC 2023] Canvas
图论建模好题.
注意值域 \(x,y\in\{1,2\}\).
正向考虑,后面的操作会覆盖前面的,意味着后面的操作为 \(2\),前面的操作为 \(1\) 一定是不劣的. 那么 \((l=1,r=1)\) 操作和 \((l=2,r=2)\) 操作的顺序就搞定了. 现在考虑怎么执行 \((l=1,r=2)\) 与 \((l=2,r=1)\) 使得答案最优.
对于考虑执行 \((u=2,v=1)\) 和 \((v=2,w=1)\) 操作的顺序,明显先执行前者更优,因为后者 \(v=2\) 覆盖了 \(v=1\).
这意味着,我们以选定的一个操作 \((s=2,t=1)\) 作为开始,后面的一系列操作顺序都是确定好的,一定是上一次操作 \(t=1\) ,下一次操作 \(t=2\) 更优. 这种单向确定的关系可以直接用有向边 \((s,t)\) 来表示,一条有向路径就是最优的操作顺序.
但是操作的最优顺序所构成的不仅仅只是有向路径,而是构成了一个一般的有向图. 这个有向图由若干强连通分量构成,每个强连通分量都蕴含其中任何一个操作作为开始都可以让除了最后一个点的每个点被赋值为 \(2\). 这时候最优的操作顺序就是使最后一个点存在 \((l=2,r=2)\) 操作将其赋值为 \(2\) 的.
但是最后一个点不好维护,所以将前面所有的建边反向,认为所有 \((l=1,r=2)\) 操作表示 \(l\) 向 \(r\) 连了一条有向边,就转换为找到一个存在操作 \((l=2,r=2)\) 覆盖的点作为开始,按顺序将这个强连通分量的每个点加入操作序列中. 记得加完之后对操作序列进行翻转还原.
最后一个问题是强连通分量彼此之间以什么样的顺序遍历?这个有向图由若干 DAG 构成,对于每个 DAG,由一个点开始要可以遍历整个 DAG,这个点显然应该属于 DAG 中入度为 \(0\) 的强连通分量.
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
int T, n, m, a[maxn]; int cnt1, now, sum, pre[maxn], ord[maxn]; bool is2[maxn];
struct OPT{int l, x, r, y;} opt[maxn];
int d[maxn];
int tot, h[maxn];
struct edge{int u, v, nxt, id;} e[maxn];
void add(int u, int v, int id) {e[++tot].id = id, e[tot].u = u, e[tot].v = v, e[tot].nxt = h[u], h[u] = tot; return;}
int cnts, bl[maxn];
int cnt, dfn[maxn], low[maxn]; bool ins[maxn]; stack<int> s;
void tarjan(int u) {
dfn[u] = low[u] = cnt++, s.push(u), ins[u] = true;
for(int i = h[u], v; i; i = e[i].nxt) {
v = e[i].v;
if(!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
else if(ins[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u]) {
cnts++;
for(int v = 0; v != u; ins[v] = false) v = s.top(), s.pop(), bl[v] = cnts;
} return;
}
bool vis[maxn];
void dfs(int u) {
vis[u] = true;
for(int i = h[u], v, id; i; i = e[i].nxt) {
v = e[i].v, id = e[i].id;
ord[++now] = id; if(!vis[v]) dfs(v);
} return;
}
void solve() {
cin >> n >> m;
for(int i = 1, l, x, r, y; i <= m; i++) {
cin >> l >> x >> r >> y;
if((x + y) == 4) is2[l] = is2[r] = true, ord[++now] = i;
if((x + y) == 3) (x == 1) ? add(l, r, i) : add(r, l, i);
opt[i].l = l, opt[i].x = x, opt[i].r = r, opt[i].y = y;
}
for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i);
for(int i = 1, u, v; i <= tot; i++) {u = e[i].u, v = e[i].v; if(bl[u] != bl[v]) d[bl[v]]++;}
for(int i = 1; i <= n; i++) if(!d[bl[i]] && !vis[i] && is2[i]) dfs(i);
for(int i = 1; i <= n; i++) if(!d[bl[i]] && !vis[i]) dfs(i);
for(int i = 1; i <= m; i++) if((opt[i].x + opt[i].y) == 2) ord[++now] = i;
for(int i = now; i >= 1; i--) a[opt[ord[i]].l] = opt[ord[i]].x, a[opt[ord[i]].r] = opt[ord[i]].y;
for(int i = 1; i <= n; i++) sum += a[i];
cout << sum << endl;
for(int i = now; i >= 1; i--) cout << ord[i] << ((i ^ 1) ? " " : "\n");
return;
}
void clean() {
for(int i = 1; i <= n; i++) h[i] = dfn[i] = low[i] = d[i] = bl[i] = ord[i] = pre[i] = a[i] = is2[i] = vis[i] = 0;
sum = cnt = cnts = tot = now = cnt1 = 0;
return;
}
int main() {
ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> T;
while(T--) solve(), clean();
return 0;
}

浙公网安备 33010602011771号