「CodeM2018复赛」 软件包管理器 - 二分答案 + 拓扑排序

版权声明:禁止商业用途,如需转载请注明原文出处:https://hyp1231.github.io 或者 https://blog.csdn.net/hyp1231/article/details/81061028

建议访问原文出处,获得更佳浏览体验。
原文出处:https://hyp1231.github.io/2018/07/08/20180708-codem/

题意

n 个点的图,依次添加给出的 m 条有向边。
每次添加后,如果图中有环,输出 0 ;否则输出 1
n 1 e 5 , m 2 e 6

输入

第一行两个正整数 n m
之后 m 行,每行两个正整数 ( u , v ) 表示加密后的边,保证 u v

记上一个回答为 a n s ,实际的边为 ( u , v )
则有 { u = ( u + a n s ) ( mod n ) + 1 v = ( v + a n s ) ( mod n ) + 1

最开始的 a n s 0

链接

CodeM2018复赛 软件包管理器

题解

如果加了某条边后,图中出现环,那么这条边及以后的 a n s 应全为 0
因此题意即为找到 a n s 序列的 1 0 的分界线。

考虑二分答案,每次判断 1 m i d 的边构成的图是否为 DAG。
如果对 1 m i d 的边建成的图可以做拓扑排序,则是 DAG;否则说明必有环。

由于拓扑排序时间复杂度 O ( n ) ,总时间复杂度 O ( n log m )

Hint: 我们可以在读入数据的时候预处理出解密后的 ( u , v )
​ 如果上一个 a n s 0 ,则 u = ( u 1 + n ) % n
​ 否则, u = ( u 2 + n ) % n v 同理。

代码

#include <cstdio>
#include <cstring>
#include <queue>

const int N = 100010;

struct E {
    int u, v;
    E(int u = 0, int v = 0) : u(u), v(v) {}
} pre_0[N << 1], pre_1[N << 1];
// 如果上一个 ans 是 0,实际的边为 pre_0[i];否则是 pre_1[i]

int n, m;
bool vis[N];
int degree[N];      // 节点度数

int e_id, head[N];  // e_id 记录边数
struct Edge {
    int u, v, next;
    Edge(int u = 0, int v = 0, int next = 0) :u(u), v(v), next(next) {}
} e[N << 1];

inline void addedge(int a, int b) {
    e[++e_id].v = b;
    e[e_id].u = a;
    e[e_id].next = head[a];
    head[a] = e_id;
}

void build(int id) {
    memset(head, 0, sizeof(head));
    e_id = 0;
    addedge(pre_0[0].u, pre_0[0].v);    // 初始 ans 为 0
    for(int i = 1; i <= id; ++i) {      // 假设前 id 条边构成 DAG
        int u = pre_1[i].u, v = pre_1[i].v;
        addedge(u, v);
    }
}

bool topo(int id) {
    build(id);  // 建图
    int cnt = 0;
    memset(vis, 0, sizeof(vis));
    memset(degree, 0, sizeof(degree));
    ++degree[pre_0[0].v];
    for (int i = 1; i <= id; ++i)
        ++degree[pre_1[i].v];
    std::queue<int> que;
    for (int i = 1; i <= n; ++i)
        if (degree[i] == 0) {
            que.push(i);
            vis[i] = true;
            ++cnt;
        }
    while (!que.empty()) {
        int u = que.front(); que.pop();
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].v;
            if (vis[v])continue;
            --degree[v];
            if (degree[v] == 0) {
                que.push(v);
                vis[v] = true;
                ++cnt;
            }
        }
    }
    return cnt == n;
}   // 如果拓扑有序,返回 true,O(n)

int solve(int l, int r) {
    if (l == r) return l;
    int mid = (l + r + 1) >> 1;
    if (topo(mid)) return solve(mid, r);
    else return solve(l, mid - 1);
}   // 二分,返回最大的可以构成 DAG 的边数,即 [1, id] 可以构成 DAG,但 [1, id + 1] 不可以

int main() {
    scanf("%d%d", &n, &m);
    int u, v;
    for (int i = 0; i < m; ++i) {
        scanf("%d%d", &u, &v);
        --u; --v;
        if (u == 0) u = n;
        if (v == 0) v = n;  // 取模
        pre_0[i].u = u;
        pre_0[i].v = v;
    }
    for (int i = 1; i < m; ++i) {
        int u = pre_0[i].u - 1, v = pre_0[i].v - 1;
        if (u == 0) u = n;
        if (v == 0) v = n;
        pre_1[i].u = u;
        pre_1[i].v = v;
    }
    int k = solve(0, m - 1);
    for (int i = 0; i < m; ++i) {
        printf("%d\n", (i <= k));
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/hyp1231/article/details/81061028