https://codeforces.com/problemset/problem/1100/E
题目大意:给
n
n
n个点和
m
m
m条边,每条边以
(
u
,
v
,
d
i
s
)
(u,v,dis)
(u,v,dis)的形式给出,表示有一条从
u
u
u指向
v
v
v的权值为
d
i
s
dis
dis的有向边,任意取一值
v
a
l
val
val,你可以翻转边权
<
=
v
a
l
<=val
<=val的边的方向,问这个
v
a
l
val
val最小为多少时,可以得到一个没有环的有向图。输出
v
a
l
val
val,以及翻转的边的数量和翻转的边的编号。
思路:求满足题意的最小值——二分,对于 m i d mid mid,我们把边权 > m i d >mid >mid的边建立出来( < = m i d <=mid <=mid的就不用管了,因为这些边我们可以任意翻转),然后对新得到的图进行拓扑排序,判断有没有环从而缩小上下界。在拓扑排序的同时记录每个点的拓扑序号,然后遍历这 m m m条边,如果 u u u的拓扑序号 > > > v v v的拓扑序号,那么这条边就需要翻转。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;
const int maxn=1e5+5;
struct node
{
int u,v,dis;
node(){}
node(int uu,int vv,int dd)
{
u=uu,v=vv,dis=dd;
}
};
int n,m,top;
vector<int> vec[maxn];
node edge[maxn];
int Stack[maxn],ind[maxn],id[maxn],ans[maxn];
bool topo(int val)
{
top=0;
for(int i=1;i<=n;i++)
vec[i].clear(),ind[i]=0;
for(int i=0;i<m;i++)
if(edge[i].dis>val)
vec[edge[i].u].push_back(edge[i].v),ind[edge[i].v]++;
for(int i=1;i<=n;i++)
if(ind[i]==0)
Stack[++top]=i;
int tmp,len,cnt=0;
while(top!=0)
{
tmp=Stack[top--];
id[tmp]=++cnt;
len=vec[tmp].size();
for(int i=0;i<len;i++)
if(--ind[vec[tmp][i]]==0)
Stack[++top]=vec[tmp][i];
}
return cnt==n?1:0;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].dis);
int l=0,r=INF,mid;
while(l<=r)
{
mid=l+r>>1;
if(!topo(mid))
l=mid+1;
else
r=mid-1;
}
topo(l);
int len=0;
for(int i=0;i<m;i++)
if(id[edge[i].u]>id[edge[i].v])
ans[len++]=i+1;
printf("%d %d\n",l,len);
for(int i=0;i<len;i++)
printf("%d ",ans[i]);
return 0;
}