CodeForces - 532E Andrew and Taxi(二分,拓扑排序)

链接CodeForces - 532E Andrew and Taxi

题意:

给出一含 n n 个结点 m m 条边 ( 2 n 100000 , 1 m 100000 ) (2≤n≤100000, 1≤m≤100000) 的有向图,每条单向边有起点 u i u_i ,终点 v i v_i 以及反转该条边(即 u i , v i u_i,v_i 交换)需要的交通管制员 c i    ( 1 u i , v i n , 1 c i 1 0 9 ) c_i\; (1≤u_i,v_i≤n, 1≤c_i≤10^9)

一次派遣 C C 个交通管制员可以选择性地反转任意 c i C c_i\le C 的边,问若要使该有向图变为有向无环图,最小的 C C 是多少,同时输出任意一组在 C C 最小时的方案(反转边数无特殊限制)。



分析:

很明显可以用 二分的方法来找到最小的 C C ,对于一个可能的答案 m i d mid ,若 m i d mid 满足条件,则更小了找,即令 r = m i d 1 r=mid-1 ;若 m i d mid 不满足条件,则往大了找,即令 l = m i d + 1 l=mid+1 。则最后一个满足条件的 m i d mid 即为答案。

怎么判断 m i d mid 是否为可行解?

可以 将图中所有 c i m i d c_i\le mid 的边全部删除,观察残图是否有环,若有环, m i d mid 肯定不是可行解;否则,我们总能找到一个方案,把删除的边加回去(反转或不反转),使得图变为DAG,即若残图无环, m i d mid 一定为可行解。

如何找到可行方案呢?

我们只需要将残图拓扑排序(若无拓扑序,则说明是有环图,即不是可行解),将删除的边加回时,要 保证起点 u i u_i 的拓扑序先于(即小于)终点 v i v_i 的拓扑序,这样就能保证新图依旧有拓扑序(即无环)。



以下代码:

#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;
}

发布了214 篇原创文章 · 获赞 40 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Ratina/article/details/99537671
今日推荐