链接:CodeForces - 532E Andrew and Taxi
题意:
给出一含 个结点 条边 的有向图,每条单向边有起点 ,终点 以及反转该条边(即 交换)需要的交通管制员 。
一次派遣 个交通管制员可以选择性地反转任意 的边,问若要使该有向图变为有向无环图,最小的 是多少,同时输出任意一组在 最小时的方案(反转边数无特殊限制)。
分析:
很明显可以用 二分的方法来找到最小的 ,对于一个可能的答案 ,若 满足条件,则更小了找,即令 ;若 不满足条件,则往大了找,即令 。则最后一个满足条件的 即为答案。
怎么判断 是否为可行解?
可以 将图中所有 的边全部删除,观察残图是否有环,若有环, 肯定不是可行解;否则,我们总能找到一个方案,把删除的边加回去(反转或不反转),使得图变为DAG,即若残图无环, 一定为可行解。
如何找到可行方案呢?
我们只需要将残图拓扑排序(若无拓扑序,则说明是有环图,即不是可行解),将删除的边加回时,要 保证起点 的拓扑序先于(即小于)终点 的拓扑序,这样就能保证新图依旧有拓扑序(即无环)。
以下代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn=1e5+50;
int n,m,k;
struct edge
{
int u;
int v;
int c;
int next;
}e[maxn];
int head[maxn];
void addedge(int id,int u,int v,int c)
{
e[id]=edge{u,v,c,head[u]};
head[u]=id;
}
int num[maxn];
bool topo(int x)
{
queue<int> q;
int in[maxn]={0},tot=0;
for(int i=1;i<=m;i++)
{
if(e[i].c>x)
in[e[i].v]++;
}
for(int i=1;i<=n;i++)
{
if(in[i]==0)
q.push(i);
}
while(!q.empty())
{
int u=q.front();
num[u]=++tot;
q.pop();
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(e[i].c>x)
{
in[v]--;
if(in[v]==0)
q.push(v);
}
}
}
if(tot==n) //若tot不等于n,则无拓扑序(即有环)
return true;
else
return false;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,c;
scanf("%d %d %d",&u,&v,&c);
addedge(i,u,v,c);
}
int L=0,R=1e9,MID;
while(L<=R)
{
MID=(L+R)>>1;
if(topo(MID))
{
k=MID;
R=MID-1;
}
else
L=MID+1;
}
topo(k);
int ans[maxn],cnt=0;
for(int i=1;i<=m;i++) //将删除边加回
{
if(e[i].c<=k&&num[e[i].u]>num[e[i].v])
ans[++cnt]=i;
}
printf("%d %d\n",k,cnt);
for(int i=1;i<=cnt;i++)
{
if(i>1)
printf(" ");
printf("%d",ans[i]);
}
printf("\n");
return 0;
}