Codeforces Round #532 (Div. 2) E. Andrew and Taxi(二分+拓扑排序)

题目链接
题意

给你一个图1-n标号,m条有向边。
每条有向边有一个权值,代表翻转其方向所需代价。
求使得图变成无环图,翻转边权的最大值最小。

思路

二分答案,判断权值大于答案的边集是否能成环,如果不能说明答案可以再小点,否则答案可以大点。
关键是翻转哪些边比较难想,第一次感受到拓扑排序,排序二字用处。
按拓扑排序的顺序,给每个点从小到大赋权,最后判断如果起点权值大于终点权值则需要翻转。

关于评论疑惑,猜测拓扑排序应该满足一个性质
如果一个图所有边的top[u] < top[v],则该图无环,top[]表示1-n的一种排列,u表示起点,v表示终点。
画个图就感受到了?

所以这题先二分构造一个答案最小的top序列,然后选择翻转某些边满足top[u] < top[v]

代码
#include <bits/stdc++.h>
using namespace std;

int n, m;
int sum, in[100005], top[100005];
vector <int> e[100005], path;

struct Node
{
    int u, v, w;
}data[100005];

int f(int mid)
{
    sum = 0;
    for(int i = 1; i <= n; ++i) e[i].clear();
    memset(in,0,sizeof(in));
    for(int i = 1; i <= m; ++i)
    {
        if(data[i].w > mid)
        {
            e[data[i].u].push_back(data[i].v);
            ++in[data[i].v];
        }
    }
    queue<int> q;
    for(int i = 1; i <= n; ++i) if(!in[i]) q.push(i), top[i] = ++sum;

    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(auto v : e[u])
        {
            --in[v];
            if(!in[v]) q.push(v), top[v] = ++sum;
        }
    }

    for(int i = 1; i <= n; ++i) if(in[i]) return 0;
    return 1;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; ++i) scanf("%d%d%d",&data[i].u,&data[i].v,&data[i].w);
    int l = 0, r = 1e9, mid, ans;
    while(l <= r)
    {
        mid = (l+r) >> 1;
        if(f(mid)) r = mid-1, ans = mid;
        else l = mid+1;
    }
    f(ans); 
    // 每个点top都有值,且必定能够通过翻转某些边得到当前top值
    for(int i = 1; i <= m; ++i) if(data[i].w <= ans && top[data[i].u] > top[data[i].v]) path.push_back(i);

    printf("%d %d\n",ans,path.size());
    for(auto i : path) printf("%d ",i);
    return 0;
}
    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/j2_o2/article/details/86469472
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞