题目链接:http://codeforces.com/contest/1100/problem/E
题意:
给你一个n个点m条边的有向图,每条边有一个额定人数x,现在要你找到a个人,对于每条边,如果人数a大于这条边的额定人数xi,那么就可以改变这条路的方向,现在要你找出一个最小的权值a,并输出改变几条边和哪些边,使得这个有向图中没有环。
二分肯定是妥妥的需要的,但是知道了这个人数之后该怎么验证是否会出现环呢,一开始是想着搜索,但是搜索出的结果只能是最小的权值是谁,对于每条边是否能修改好像还是不能做。这个地方实在是卡了我好久,拓扑也是参考了大佬的做法的。。感觉好奇妙好神奇。拓扑序的概念还是比较少碰到的,所以这次就留下一篇题解。
做法:
在验证这个答案的时候,对于每一条权值小于你当前mid的时候,你不能加边,这条边就等于是可变可不变的,对于剩下的不能改变的边,先做一次拓扑,注意,如果拓扑完成后还有点的入度不为0的,那么就是这个图还存在环,就是mid太小。以此类推我们可以找到这个最小的最大需要人数,找到这个人数之后,我们就可以对这个数进行判断,如何判断,我们肯定这个答案下的图是没有环了的,那么我们就可以进行拓扑排序,得到每个点的拓扑序,对于每一条待修改的边,我们需要比较这条边的起点和终点的拓扑序,如果起点的大于终点,说明这条边在拓扑时起点会比终点晚一点被遍历到,那么改变方向,就不会出现环。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100005;
int n,m,mma,in[maxn],top[maxn];
struct node{
int fr,to,val;
}e[maxn];
vector<int> ve[maxn],road;
int ck(int x){
memset(in,0,sizeof(in));
for(int i=1;i<=n;i++){ve[i].clear();}
int now=0;
for(int i=1;i<=m;i++){
if(e[i].val>x){
in[e[i].to]++;
ve[e[i].fr].push_back(e[i].to);
}
}
queue<int> q;
for(int i=1;i<=n;i++){
if(!in[i]){
q.push(i);
top[i]=++now;
}
}
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=0;i<ve[u].size();i++){
int v=ve[u][i];
//printf("from=%d to=%d \n",u,v);
if(--in[v]==0){
q.push(v);
top[v]=++now;
}
}
}
for(int i=1;i<=n;i++){
if(in[i]) return 0;
}
return 1;
}
int main(){
int x,y,z,mma=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
e[i].fr=x,e[i].to=y,e[i].val=z;
mma=max(mma,z);
}
int l=0,r=mma,ans=-1;
while(l<=r){
int mid=(l+r)/2;
if(ck(mid)) {ans=mid; r=mid-1;}
else l=mid+1;
}
ck(ans);
for(int i=1;i<=m;i++){
if(e[i].val<=ans){
if(top[e[i].fr]>top[e[i].to]){
road.push_back(i);
}
}
}
printf("%d %d\n",ans,road.size());
for(int i=0;i<road.size();i++)
printf("%d%c",road[i],i==road.size()-1?'\n':' ');
return 0;
}