【模板】拟阵交
给定边集 \(E\),每条边都有一个颜色。给定数组 \(\{C_i\}\)。选出最大的子集使得:子集中找不到一个简单环;子集中颜色 \(c\) 的边的出现次数不超过 \(C[c]\)。
本题即为拟阵交的模板题,需要求解图拟阵和划分拟阵的交中最大的独立集。可以证明,它们的交还是拟阵,故使用拟阵交算法解决该问题。
请参考:从拟阵基础到 Shannon 开关游戏 - 洛谷专栏
步骤:
- 拟阵 A(图拟阵):没有简单环的集合是独立集。
- 拟阵 B(划分拟阵):颜色 \(c\) 的边的出现次数都不超过 \(C[c]\) 的集合是独立集。
- 据此写出两个拟阵的代码,支持设定一个 \(I\)(
build),和给定 \(x\) 返回 \(I\cup\{x\}\) 是否在拟阵中(oracle)。 - 从 \(I=\varnothing\) 开始,重复以下步骤:
- 建一张空的有向二分图,\(I\) 是左部点,\(E\setminus I\) 是右部点。
- 枚举 \(i\in I\),将两个拟阵的 \(I'\) 设置为当前的 \(I\setminus\{i\}\)。枚举 \(j\not\in I\),若拟阵 A
oracle(j)则连边 \(i\to j\);若拟阵 Boracle(j)则连边 \(j\to i\); - 准备进行 BFS 找增广路。设置拟阵 A 的 \(I'=I\),将所有 \(i\not\in I\) 且能
oracle的 \(i\) 入队。 - 进行 BFS,注意要维护最短的增广路,最短就是点数最少的意思;还要维护路径的前驱。
- 设置拟阵 B 的 \(I'=I\),枚举所有 \(i\not\in I\) 且能
oracle的 \(i\),找出最短路最短的那个 \(i\)。 - 从那个最短的点往回跳,将增广路上所有点在 \(I\) 的状态取反。
- 算法无法找出增广路时,算法结束,\(I\) 就是答案。
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#define endl "\n"
#endif
using LL = long long;
template <int N>
struct unionset {int fa[N], siz[N];unionset() { iota(fa, fa + N, 0), fill_n(siz, N, 1); }int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }bool merge(int x, int y) {x = find(x), y = find(y);if (x == y) return false;if (siz[x] < siz[y]) swap(x, y);fa[y] = x, siz[x] += siz[y];return true;}
};
constexpr int N = 510;
struct edge {int u, v, w;
};
int n, m, c[N], K;
bool vis[N];
edge e[N];
struct matroid_graph {unionset<N> dsu;void build(int ban) {dsu = unionset<N>();for (int i = 1; i <= m; i++) if (vis[i] && i != ban) {dsu.merge(e[i].u, e[i].v);}}bool oracle(int x) {return dsu.find(e[x].u) != dsu.find(e[x].v);}
};
struct matroid_color {int cnt[N];void build(int ban) {memset(cnt, 0, sizeof cnt);for (int i = 1; i <= m; i++) if (vis[i] && i != ban) {cnt[e[i].w] += 1;}}bool oracle(int x) {return cnt[e[x].w] < c[e[x].w];}
};
int main() {
#ifndef LOCALcin.tie(nullptr)->sync_with_stdio(false);
#ifndef NF
#endif
#endifcin >> n >> m >> K;for (int i = 1; i <= K; i++) cin >> c[i];for (int i = 1, u, v, w; i <= m; i++) {cin >> u >> v >> w;e[i] = {u, v, w};}matroid_graph m1;matroid_color m2;memset(vis, false, sizeof vis);static bool g[N][N];while (true) {memset(g, false, sizeof g);for (int i = 1; i <= m; i++) if (vis[i]) {m1.build(i), m2.build(i);for (int j = 1; j <= m; j++) if (!vis[j]) {if (m1.oracle(j)) g[i][j] = true;if (m2.oracle(j)) g[j][i] = true;}}static int pre[N], dis[N];memset(dis, 0x3f, sizeof dis);queue<int> q;m1.build(0);for (int i = 1; i <= m; i++) if (!vis[i] && m1.oracle(i)) q.push(i), pre[i] = 0, dis[i] = 0;while (!q.empty()) {int u = q.front(); q.pop();for (int v = 1; v <= m; v++) if (g[u][v] && dis[v] > dis[u] + 1) dis[v] = dis[u] + 1, pre[v] = u, q.push(v);}m2.build(0);int rp = -1;for (int i = 1; i <= m; i++) if (!vis[i] && m2.oracle(i) && (rp == -1 || dis[rp] > dis[i])) rp = i;if (rp == -1 || dis[rp] > 1e9) break;while (rp) vis[rp] ^= 1, rp = pre[rp];}int cnt = 0;for (int i = 1; i <= m; i++) cnt += !vis[i];cout << cnt << endl;for (int i = 1; i <= m; i++) if (!vis[i]) cout << i << " ";cout << endl;return 0;
}
所以谁能告诉我,为什么拟阵会有一个甲骨文(oracle)操作呢?