原题: http://codeforces.com/problemset/problem/1100/E
题意:
有n个点,m条有向边。每条边有个花费,当你翻转这条边后所需要花费的价值。翻转多条边的花费为这些边的max。问最小的花费使该图中不存在环。
解析:
首先二分最终答案应该没问题,小于等于答案值的边都可以进行调整。
我做的时候推了一个结论:若原图中没有环,则加一条有向边后,一定存在一种方向使得添加后的图中没有环。
证明:
也就是说,如果不能改变的那些边不能构成环,则剩下的边一定可以避免构成环(看成一条一条加入)。那么问题就是判环以及剩下的边的朝向。
判环直接拓扑排序即可,判断直到没有入度为0的点后是否还有点。
再来看剩下边的朝向问题。设没有被访问的点x,被访问的点y。对于单单由x构成的边集,按照拓扑序可以保证一不会出现环。对于x与y共同构成的边集,将边的方向朝向y即可。
那么只需要让被指向的那个y的拓扑序大于其他点即可,所以在遍历的时候,将没有被访问的点在拓扑排序中放在前面即可。
最后按照拓扑序小的指向大的,就可以避免环了。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,m;
int in[maxn],topn[maxn];
vector<int>P[maxn];
struct node {
int x,y,v,id;
bool operator<(const node &r)const {
return v>r.v;
}
} e[maxn];
bool check(int V) {
for(int i=1; i<=n; i++)
in[i]=0,P[i].clear();
for(int i=1; i<=m&&e[i].v>V; i++) {
int a=e[i].x,b=e[i].y;
in[b]++;
P[a].push_back(b);
}
queue<int>Q;
for(int i=1; i<=n; i++)
if(!in[i])
Q.push(i);
int cnt=0;
while(!Q.empty()) {
int id=Q.front();
Q.pop();
topn[id]=++cnt;
for(int i=0; i<P[id].size(); i++) {
int to=P[id][i];
in[to]--;
if(!in[to]) {
Q.push(to);
}
}
}
for(int i=1; i<=n; i++)
if(in[i])
return 0;
return 1;
}
int main() {
scanf("%d%d",&n,&m);
int l=-1,r=-1;
for(int i=1; i<=m; i++) {
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v);
e[i].id=i;
r=max(r,e[i].v+1);
}
sort(e+1,e+1+m);
while(r-l>1) {
int mid=l+r>>1;
if(check(mid))
r=mid;
else
l=mid;
}
check(r);//最后一步变化l就会出错
vector<int>ans;
for(int i=1; i<=m; i++) {
int a=e[i].x,b=e[i].y;
if(topn[a]>topn[b])
ans.push_back(e[i].id);
}
printf("%d %d\n",r,ans.size());
for(int i=0; i<ans.size(); i++)
printf("%d%c",ans[i],(i==ans.size()-1?'\n':' '));
}