题意
给一张无向图,每条边有颜色和边权。选择一个匹配删去,使得每个点相邻的所有边没有颜色相同的。最小化删去匹配的最大边权并输出方案。
n
,
m
≤
5
×
1
0
4
n, m\leq 5\times 10^4
n,m≤5×104
思路
- 首先简单转化成2-sat问题。
- 然后二分答案,现在是钦点了一些边不选,判断是否有方案即可。
2-sat问题基于对称性的解法
- 先将每条边拆成x与x’,表示选或不选。然后将所有形如选了a就一定要选b的关系连上一条有向边<a,b>。
- (下面x’指的是x的反点。)
- 建一些虚点辅助连边,不影响整个图的连通性
- 跑tarjan,若x与x’在同一个连通分量里面,则无解。
- 这个图很对称,若x能到达y,则y’能到达x’.
- 因此将所有强制选的点在dag上一路选下去,并将走到的所有点的反点标记不选。
- 若已有反点标记选,则无解。
- 否则,再按照拓扑序逆序选择每个还未确定的点做上述选的过程即可。(无解的情况都存在x走到x’,此时由于是按照拓扑序逆序选的,因此必定是有解的。)
- 其实强制一个点选,只要其反点向其连边即可。(不过这样要重建,很慢)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
pair<int, int> key[N];
int tim[N], fx[N];
map<int, int> cnt[N];
vector<int> to[N], zto[N];
void link(int x, int y) {
// printf("%d %d\n", x, y);
to[x].push_back(y);
}
int zz;
void add(int u, int c, int i) {
if (cnt[u][c] != 0) {
if (cnt[u][c] == -1) {
printf("No\n"); exit(0);
}
link(i + m, cnt[u][c]);
link(cnt[u][c] + m, i);
cnt[u][c] = -1;
} else {
cnt[u][c] = i;
}
}
int dfn[N], low[N], stm;
int S[N], ins[N], cir[N], tot;
vector<int> ps[N];
void tarjan(int x) {
dfn[x] = low[x] = ++ stm;
S[++S[0]] = x; ins[x] = 1;
for(int y : to[x]) {
if (dfn[y]) {
if (ins[y]) low[x] = min(low[x], low[y]);
} else {
tarjan(y);
low[x] = min(low[x], low[y]);
}
}
if (low[x] == dfn[x]) {
do {
ins[S[S[0]]] = 0;
cir[S[S[0]]] = x;
if (S[S[0]] <= 2 * m)
ps[x].push_back(S[S[0]]);
} while(S[S[0]--] != x);
}
}
int d[N], Q[N];
vector<int> es[N];
void topo() {
int h = 0, t = 0;
for(int i = 1; i <= tot; i++) if (cir[i] == i && d[i] == 0) Q[++t] = i;
while (h < t) {
int x = Q[++h];
for(int y : zto[x]) {
if(--d[y] == 0) Q[++t] = y;
}
}
Q[0] = h;
}
int vis[N];
bool enable(int x) {
if (vis[x] == -1) return 0;
if (vis[x] == 1) return 1;
vis[x] = 1;
for(int e : ps[x]) {
if (vis[cir[fx[e]]] == 1) return 0;
vis[cir[fx[e]]] = -1;
}
for(int y : zto[x]) if(!enable(y)) return 0;
return 1;
}
bool ok(int x) {
memset(vis, 0, sizeof vis);
for(int i = 1; i <= m; i++) if (tim[i] > x) {
if (!enable(cir[i + m])) return 0;
}
for(int i = Q[0]; i; i--) {
int x = Q[i];
if (x > 2 * m) {
if (ps[x].size() == 0) continue;
x = ps[x].back();
}
if (vis[cir[x]] == 0) {
enable(cir[x]);
}
}
return 1;
}
int main() {
freopen("d.in","r",stdin);
cin >> n >> m; tot = 2 * m;
for(int i = 1; i <= m; i++) {
int u, v, c, t; scanf("%d %d %d %d", &u, &v, &c, &t);
tim[i] = t;
es[u].push_back(i);
es[v].push_back(i);
add(u, c, i);
add(v, c, i);
}
for(int i = 1; i <= n; i++) {
int pre = 0;
for(int j : es[i]) {
int u = ++tot;
if (pre) {
link(j, pre);
link(u, pre);
}
link(u, j + m);
pre = u;
}
pre = 0;
for (int z = es[i].size() - 1; ~z; z--) {
int j = es[i][z];
int u = ++tot;
if (pre) {
link(j, pre);
link(u, pre);
}
link(u, j + m);
pre = u;
}
}
for(int i = 1; i <= tot; i ++) if (!dfn[i])
tarjan(i);
for(int i = 1; i <= m; i++) {
fx[i] = i + m;
fx[i + m] = i;
if (cir[i] == cir[i + m]) {
printf("No"); return 0;
}
}
for(int x = 1; x <= tot; x++) {
for(int y : to[x]) if(cir[x] != cir[y]) {
zto[cir[x]].push_back(cir[y]);
d[cir[y]]++;
}
}
topo();
cerr << ok(641991574) << endl;
int l = 0, r = 1e9, ans = 0;
while (l <= r) {
if (ok(l + r >> 1)) {
ans = r = l + r >> 1;
r--;
} else {
l = (l + r >> 1) + 1;
}
}
printf("Yes\n");
ok(ans);
int cnt = 0;
for(int i = 1; i <= m; i++) if (vis[cir[i]] == 1) {
cnt++;
}
cout << ans << " " << cnt << endl;
for(int i = 1; i <= m; i++) if (vis[cir[i]] == 1) {
printf("%d ", i);
}
}