CF1100E Andrew and Taxi 题解

Description

洛谷传送门

Solution

看到最大值最小这样的字眼,自然想到二分答案

我们二分所选边权的最大权值,那么比这个值大的边都不能反向,小于等于它的边都可以选择反向。

设当前二分到的权值为 \(mid\),我们不用去管小于等于 \(mid\) 的边(总有方法让它形成不了环),所以我们把大于 \(mid\) 的边全都加进去,利用拓扑排序来判断是否有负环。

具体方法:把进队的点的个数统计出来为 \(sum\),判断是否等于 \(n\)。

  • 若 \(sum < n\),有环。

  • 若 \(sum = n\),无环。

符合条件的情况下我们就该找哪些边需要反向了。

由于题目中并没有最小化反向边的数量之类的要求,所以我们可以把所有会造成环的边全部反向。

那么有哪些边会形成环呢?

还是拓扑排序,当我们对大于 \(mid\) 的边所形成的图进行拓扑排序时,记录一下它在拓扑序中的位置,即拓扑序。

假设有 \(x\),\(y\) 两点,且拓扑序 \(dfn_y < dfn_x\),此时如果有一条从 \(x\) 连到 \(y\) 的边,那么这条边就会形成负环,就要给它反向。

所以在合法的 \(mid\) 下遍历一遍所有的边,判一下拓扑序即可,注意边权要小于等于 \(mid\)。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>

using namespace std;

const int N = 1e5 + 10;
struct node{
    int u, v, w, nxt;
}edge[N];
int head[N], tot;
int n, m, tim;
bool vis[N], t[N];
int in[N], dfn[N];
int ans[N], cnt;

inline void add(int x, int y, int z){
    edge[++tot] = (node){x, y, z, head[x]};
    head[x] = tot;
}

inline int topo(int now){
    int sum = 0;
    queue <int> q;
    for(int i = 1; i <= n; ++i)
        if(!in[i]) q.push(i);
    while(!q.empty()){
        int x = q.front();
        q.pop();
        sum++, dfn[x] = ++tim;
        for(int i = head[x]; i; i = edge[i].nxt){
            int y = edge[i].v;
            if(edge[i].w <= now) continue;
            if(!(--in[y])) q.push(y);
        }
    }
    return sum >= n;
}

inline bool check(int now){
    memset(in, 0, sizeof(in));
    memset(dfn, 0, sizeof(dfn));
    tim = 0;
    for(int i = 1; i <= m; ++i)
        if(edge[i].w > now) in[edge[i].v]++;
    if(!topo(now)) return 0;
    cnt = 0;
    for(int i = 1; i <= n; ++i)
        if(!dfn[i]) dfn[i] = ++tim;
    for(int i = 1; i <= m; ++i){
        int u = edge[i].u, v = edge[i].v;
        if(edge[i].w <= now && dfn[u] > dfn[v])
            ans[++cnt] = i;
    }
    return 1;
}

int main(){
    int l = 0, r = 0, maxs = 0;
    scanf("%d%d", &n, &m);
    for(int i = 1, u, v, w; i <= m; ++i){
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w);
        r = max(r, w);
    }
    while(l <= r){
        int mid = (l + r) >> 1;
        if(check(mid)) maxs = mid, r = mid - 1;
        else l = mid + 1;
    }
    printf("%d %d\n", maxs, cnt);
    sort(ans + 1, ans + 1 + cnt);
    for(int i = 1; i <= cnt; ++i)
        printf("%d ", ans[i]);
    puts("");
    return 0;
}

End

上一篇:开心!偶得机会内推顺丰:复习了226页PDF的我二面顺利拿下18K的offer!


下一篇:进入正在运行的Docker容器的4种方