题目链接
题意
给你一个图1-n标号,m条有向边。
每条有向边有一个权值,代表翻转其方向所需代价。
求使得图变成无环图,翻转边权的最大值最小。
思路
二分答案,判断权值大于答案的边集是否能成环,如果不能说明答案可以再小点,否则答案可以大点。
关键是翻转哪些边比较难想,第一次感受到拓扑排序,排序二字用处。
按拓扑排序的顺序,给每个点从小到大赋权,最后判断如果起点权值大于终点权值则需要翻转。
关于评论疑惑,猜测拓扑排序应该满足一个性质
如果一个图所有边的top[u] < top[v],则该图无环,top[]表示1-n的一种排列,u表示起点,v表示终点。
画个图就感受到了?
所以这题先二分构造一个答案最小的top序列,然后选择翻转某些边满足top[u] < top[v]
代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
int sum, in[100005], top[100005];
vector <int> e[100005], path;
struct Node
{
int u, v, w;
}data[100005];
int f(int mid)
{
sum = 0;
for(int i = 1; i <= n; ++i) e[i].clear();
memset(in,0,sizeof(in));
for(int i = 1; i <= m; ++i)
{
if(data[i].w > mid)
{
e[data[i].u].push_back(data[i].v);
++in[data[i].v];
}
}
queue<int> q;
for(int i = 1; i <= n; ++i) if(!in[i]) q.push(i), top[i] = ++sum;
while(!q.empty())
{
int u = q.front();
q.pop();
for(auto v : e[u])
{
--in[v];
if(!in[v]) q.push(v), top[v] = ++sum;
}
}
for(int i = 1; i <= n; ++i) if(in[i]) return 0;
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; ++i) scanf("%d%d%d",&data[i].u,&data[i].v,&data[i].w);
int l = 0, r = 1e9, mid, ans;
while(l <= r)
{
mid = (l+r) >> 1;
if(f(mid)) r = mid-1, ans = mid;
else l = mid+1;
}
f(ans);
// 每个点top都有值,且必定能够通过翻转某些边得到当前top值
for(int i = 1; i <= m; ++i) if(data[i].w <= ans && top[data[i].u] > top[data[i].v]) path.push_back(i);
printf("%d %d\n",ans,path.size());
for(auto i : path) printf("%d ",i);
return 0;
}