【ACWing】368. 银河

题目地址:

https://www.acwing.com/problem/content/370/

银河中的恒星浩如烟海,但是我们只关注那些最亮的恒星。我们用一个正整数来表示恒星的亮度,数值越大则恒星就越亮,恒星的亮度最暗是 1 1 1。现在对于 N N N颗我们关注的恒星,有 M M M对亮度之间的相对关系已经判明。你的任务就是求出这 N N N颗恒星的亮度值总和至少有多大。

输入格式:
第一行给出两个整数 N N N M M M。之后 M M M行,每行三个整数 T , A , B T,A,B T,A,B,表示一对恒星 ( A , B ) (A,B) (A,B)之间的亮度关系。恒星的编号从 1 1 1开始。
如果 T = 1 T=1 T=1,说明 A A A B B B亮度相等。
如果 T = 2 T=2 T=2,说明 A A A的亮度小于 B B B的亮度。
如果 T = 3 T=3 T=3,说明 A A A的亮度不小于 B B B的亮度。
如果 T = 4 T=4 T=4,说明 A A A的亮度大于 B B B的亮度。
如果 T = 5 T=5 T=5,说明 A A A的亮度不大于 B B B的亮度。

输出格式:
输出一个整数表示结果。若无解,则输出 − 1 −1 1

数据范围:
N ≤ 100000 , M ≤ 100000 N≤100000,M≤100000 N100000,M100000

可以用差分约束建图,然后求最长路的方法来做,参考https://blog.csdn.net/qq_46105170/article/details/116776382,但是更好的做法是先求强连通分量,然后按拓扑序递推来做(这里的递推本质上也是求最长路,但是复杂度相比于SPFA更优)。首先建图还是和参考链接里的差分约束做法相同,然后求强连通分量,如果同一个分量内存在正边,那么一定存在正环,则无解;否则的话同一个分量里的所有的边长度都是 0 0 0,即点的亮度都相等。接着只需要按照强连通分量的拓扑序递推最长路即可。代码如下:

#include <cstring>
#include <iostream>
using namespace std;

const int N = 100010, M = 600010;
int n, m;
int h[N], hs[N], e[M], ne[M], w[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, scc_size[N];
int dist[N];

void add(int h[], int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void tarjan(int u) {
    dfn[u] = low[u] = timestamp++;
    stk[top++] = u, in_stk[u] = true;

    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (!dfn[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        } else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u]) {
        scc_cnt++;
        int y;
        do {
            y = stk[--top];
            in_stk[y] = false;
            id[y] = scc_cnt;
            scc_size[scc_cnt]++;
        } while (y != u);
    }
}

int main() {
    cin >> n >> m;
    memset(h, -1, sizeof h);
    memset(hs, -1, sizeof h);

	// 亮度最小是1,所以开一个源点0,从0到别的点连一条长1的边
    for (int i = 1; i <= n; i++) add(h, 0, i, 1);
    while (m--) {
        int t, a, b;
        scanf("%d%d%d", &t, &a, &b);
        if (t == 1) add(h, b, a, 0), add(h, a, b, 0);
        else if (t == 2) add(h, a, b, 1);
        else if (t == 3) add(h, b, a, 0);
        else if (t == 4) add(h, b, a, 1);
        else if (t == 5) add(h, a, b, 0);
    }

    tarjan(0);

    bool success = true;
    // 枚举所有的边,建缩点后的新图
    for (int i = 0; i <= n; i++) {
        for (int j = h[i]; ~j; j = ne[j]) {
            int k = e[j];
            int a = id[i], b = id[k];
            if (a == b) {
            	// 如果某个强连通分量内有长度为正的边,那么必然存在正环,则无解
                if (w[j] > 0) {
                    success = false;
                    break;
                }
            } else add(hs, a, b, w[j]);
        }

        if (!success) break;
    }

    if (!success) puts("-1");
    else {
    	// 按拓扑序做递推,求最长路
        for (int i = scc_cnt; i; i--) {
            for (int j = hs[i]; ~j; j = ne[j]) {
                int k = e[j];
                dist[k] = max(dist[k], dist[i] + w[j]);
            }
        }

        long res = 0;
        // 总亮度是每个连通分量的亮度乘以连通分量点数求总和
        for (int i = 1; i <= scc_cnt; i++) res += (long) dist[i] * scc_size[i];
        printf("%ld", res);
    }
    
    return 0;
}

时间复杂度 O ( n + m ) O(n+m) O(n+m) n n n m m m分别是图的点数和边数),空间 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值