Description
Solution
看到最大值最小这样的字眼,自然想到二分答案。
我们二分所选边权的最大权值,那么比这个值大的边都不能反向,小于等于它的边都可以选择反向。
设当前二分到的权值为 (mid),我们不用去管小于等于 (mid) 的边(总有方法让它形成不了环),所以我们把大于 (mid) 的边全都加进去,利用拓扑排序来判断是否有负环。
具体方法:把进队的点的个数统计出来为 (sum),判断是否等于 (n)。
-
若 (sum < n),有环。
-
若 (sum = n),无环。
符合条件的情况下我们就该找哪些边需要反向了。
由于题目中并没有最小化反向边的数量之类的要求,所以我们可以把所有会造成环的边全部反向。
那么有哪些边会形成环呢?
还是拓扑排序,当我们对大于 (mid) 的边所形成的图进行拓扑排序时,记录一下它在拓扑序中的位置,即拓扑序。
假设有 (x),(y) 两点,且拓扑序 (dfn_y < dfn_x),此时如果有一条从 (x) 连到 (y) 的边,那么这条边就会形成负环,就要给它反向。
所以在合法的 (mid) 下遍历一遍所有的边,判一下拓扑序即可,注意边权要小于等于 (mid)。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
struct node{
int u, v, w, nxt;
}edge[N];
int head[N], tot;
int n, m, tim;
bool vis[N], t[N];
int in[N], dfn[N];
int ans[N], cnt;
inline void add(int x, int y, int z){
edge[++tot] = (node){x, y, z, head[x]};
head[x] = tot;
}
inline int topo(int now){
int sum = 0;
queue <int> q;
for(int i = 1; i <= n; ++i)
if(!in[i]) q.push(i);
while(!q.empty()){
int x = q.front();
q.pop();
sum++, dfn[x] = ++tim;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(edge[i].w <= now) continue;
if(!(--in[y])) q.push(y);
}
}
return sum >= n;
}
inline bool check(int now){
memset(in, 0, sizeof(in));
memset(dfn, 0, sizeof(dfn));
tim = 0;
for(int i = 1; i <= m; ++i)
if(edge[i].w > now) in[edge[i].v]++;
if(!topo(now)) return 0;
cnt = 0;
for(int i = 1; i <= n; ++i)
if(!dfn[i]) dfn[i] = ++tim;
for(int i = 1; i <= m; ++i){
int u = edge[i].u, v = edge[i].v;
if(edge[i].w <= now && dfn[u] > dfn[v])
ans[++cnt] = i;
}
return 1;
}
int main(){
int l = 0, r = 0, maxs = 0;
scanf("%d%d", &n, &m);
for(int i = 1, u, v, w; i <= m; ++i){
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
r = max(r, w);
}
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)) maxs = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d %d
", maxs, cnt);
sort(ans + 1, ans + 1 + cnt);
for(int i = 1; i <= cnt; ++i)
printf("%d ", ans[i]);
puts("");
return 0;
}