【模板】拟阵交
【模板】拟阵交
给定边集 \(E\),每条边都有一个颜色。给定数组 \(\{C_i\}\)。选出最大的子集使得:子集中找不到一个简单环;子集中颜色 \(c\) 的边的出现次数不超过 \(C[c]\)。
本题即为拟阵交的模板题,需要求解图拟阵和划分拟阵的交中最大的独立集。可以证明,它们的交还是拟阵,故使用拟阵交算法解决该问题。
请参考:从拟阵基础到 Shannon 开关游戏 - 洛谷专栏
拟阵的定义
来源是上面贴的参考资料。
拟阵是一个对向量空间中线性独立概念的概括与归纳的数学结构。
通俗的说,拟阵是描述独立关系的结构。
一个拟阵 \(\mathcal M = \langle X,\mathcal I\rangle\),其中 \(X\) 被称为 Ground Set,\(\mathcal I\) 被称为 Family of Sets,即 \(X\) 的独立子集的集合。如果一个集合 \(I \in \mathcal I\) (请注意 \(I\) 和 \(\mathcal I\) 之间的区别,一个是 I,一个是 \mathcal I,他们是不同的),则他是 \(X\) 的一个独立子集。
【性质】一个拟阵必须有下列性质:
- \(X\) 是一个有穷集合。
- (遗传性)一个独立子集的所有子集都是独立子集,即对于所有的 \(I \in \mathcal I, I' \subseteq I\),则有 \(I' \in \mathcal I\)。空集必须是独立集。
- (交换性质)若 \(A,B \in \mathcal I, |A| < |B|\),则 \(\exists x \in B \setminus A\) 使得 \(A + \{x\} \in \mathcal I\)。
如果我们需要证明一个形如 \(\langle X, \mathcal I\rangle\) 的东西是拟阵,我们只需要证明他满足以上 3 条性质即可。
这是一个拟阵:\(\mathcal M = \langle \{x, y, z\},\mathcal \{\varnothing, \{x\}, \{y\},\{z\},\{x, y\}, \{x, z\}\}\rangle\)。
拟阵的秩就是 \(X\) 的最大独立子集的大小。
求拟阵的秩
其实是 Kruskal 算法的一种体现。也就是先把 \(X\) 中的元素按照权值排序(如果元素带权),然后维护一个 \(I\) 初始为空,枚举 \(X\) 中元素,如果它加入 \(I\) 后仍然在 \(\mathcal I\) 中,则加入;否则以后也不会再加入它了,可以扔掉。最后得到的 \(I\) 的大小就是拟阵的秩。
拟阵交
拟阵和拟阵的交不一定是拟阵。当它真的不是拟阵的时候,我们使用拟阵交算法求出它的秩;否则使用刚才说的算法直接求拟阵的秩就好了。
以下是无权拟阵交算法的具体步骤。由于带权拟阵交还不知道为什么对,暂时按下不表。
- 拟阵 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 LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#ifndef NF
#endif
#endif
cin >> 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;
}
所以谁能告诉我,为什么拟阵(matroid)会有一个甲骨文(oracle)操作呢?
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://chuna2.787528.xyz/caijianhong/p/19514164/template-matroid
浙公网安备 33010602011771号