• AcWing 368. 银河


    \(AcWing\) \(368\). 银河

    题目传送门

    一、题目描述

    银河中的恒星浩如烟海,但是我们只关注那些 最亮的恒星

    我们用一个正整数来表示恒星的亮度,数值越大则恒星就越亮,恒星的亮度最暗是 \(1\)

    现在对于 \(N\) 颗我们关注的恒星,有 \(M\) 对亮度之间的相对关系已经判明。

    你的任务就是求出这 \(N\) 颗恒星的 亮度值总和至少有多大

    输入格式
    第一行给出两个整数 \(N\)\(M\)

    之后 \(M\) 行,每行三个整数 \(T,A,B\),表示一对恒星 \((A,B)\) 之间的亮度关系。恒星的编号从 \(1\) 开始。

    如果 \(T=1\),说明 \(A\)\(B\) 亮度相等。
    如果 \(T=2\),说明 \(A\) 的亮度小于 \(B\) 的亮度。
    如果 \(T=3\),说明 \(A\) 的亮度不小于 \(B\) 的亮度。
    如果 \(T=4\),说明 \(A\) 的亮度大于 \(B\) 的亮度。
    如果 \(T=5\),说明 \(A\) 的亮度不大于 \(B\) 的亮度。

    输出格式
    输出一个整数表示结果。

    若无解,则输出 \(−1\)

    二、差分约束解法

    \(N\) 颗恒星的亮度值总和至少有多大

    求最小->求所有下界的最大->最长路 √

    求最大->求所有上界的最小->最短路 ×

    最长路
    \(dist[j] ≥ dist[t] + w[i]\)

    \(T=1: A=B => A≥B\) \(B≥A\)
    \(T=2: A<B => B≥A+1\)
    \(T=3: A≥B => A≥B\)
    \(T=4: A>B => A≥B+1\)
    \(T=5: A≤B => B≥A\)

    \(spfa\)最长路 - 做完后每个点的距离就是最小值

    • 边是正的 - 存在正环 => 无解
    • 有解
      必须有绝对值
      超级源点(能到所有边)
      \(x[i]≥x[0]+1\)

    差分约束代码

    通过了 10/11个数据

    本题数据范围
    \(N≤100000,M≤100000\)

    糖果 那道题一样一样的数据范围,肯定是测试数据进行了加强,导致卡掉了\(spfa\),无论你是\(stack\)优化,\(slf\)优化,还是双端队列优化,还是什么\(dfs\)优化,一概无效,真是醉了,就为了让我学习\(tarjan+SCC+DAG\)值得这么费劲吗?我学不就完了吗??

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N = 100010, M = 300010;
    //与AcWing 1169. 糖果 这道题一模一样,连测试用例都一样
    
    stack<int> q; //有时候换成栈判断环很快就能
    LL dist[N];
    bool st[N];
    int cnt[N];
    int n, m; //表示点数和边数
    //邻接表
    int e[M], h[N], idx, w[M], ne[M];
    void add(int a, int b, int c) {
        e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
    }
    
    bool spfa() {                         //求最长路,所以判断正环
        memset(dist, -0x3f, sizeof dist); //初始化为-0x3f
        //差分约束从超级源点出发
        dist[0] = 0;
        q.push(0);
        st[0] = true;
    
        while (q.size()) {
            int u = q.top();
            q.pop();
            st[u] = false;
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                if (dist[j] < dist[u] + w[i]) { //求最长路
                    dist[j] = dist[u] + w[i];
                    cnt[j] = cnt[u] + 1;
                    //注意多加了超级源点到各各节点的边
                    if (cnt[j] >= n + 1) return false;
                    if (!st[j]) {
                        q.push(j);
                        st[j] = true;
                    }
                }
            }
        }
        return true;
    }
    
    int main() {
        memset(h, -1, sizeof h);
        scanf("%d %d", &n, &m);
        for (int i = 0; i < m; i++) {
            int op, a, b; // op为选择
            scanf("%d %d %d", &op, &a, &b);
            if (op == 1) /** a == b  =>  (a >= b , b >= a)  */
                add(a, b, 0), add(b, a, 0);
            else if (op == 2) /**  b >= a + 1         */
                add(a, b, 1);
            else if (op == 3) /**  a >= b         */
                add(b, a, 0);
            else if (op == 4) /**  a >= b + 1        */
                add(b, a, 1);
            else /** b >= a   */
                add(a, b, 0);
        }
    
        /** xi >= x0 + 1 (每个小朋友都要至少一个糖果)*/
        //将所有节点与超级源点x0相连
        for (int i = 1; i <= n; ++i) add(0, i, 1);
    
        if (!spfa())
            puts("-1");
        else {
            LL res = 0;
            for (int i = 1; i <= n; ++i) res += dist[i];
            printf("%lld\n", res);
        }
        return 0;
    }
    

    三、强连通分量思路

    分析
    用差分约束可能被卡,用 \(SCC\) 稳定线性,但并非所有差分约束都能通过 \(SCC\) 解决。本题条件:

    • 无负权边
    • 问是否存在正环
    • 问每个点到其它点的最长距离

    性质:

    • 环一定在 \(SCC\) 中,在无负权边的前提下,若 \(SCC\) 中存在一条正权边,则必存在正环
    • 若不存在正环,则 \(SCC\) 中每条边权都为 \(0\),等价于每个点都相等
    • \(DAG\) 上求最长距离只需按拓扑序递推

    强连通分量代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int N = 100010, M = 600010;
    
    int n, m;
    int h[N], hs[N], e[M], ne[M], w[M], idx;
    void add(int h[], int a, int b, int c) {
        e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
    }
    
    int dfn[N], low[N], ts, stk[N], top, in_stk[N];
    int id[N], scc_cnt, sz[N];
    int dist[N];
    
    // tarjan求scc
    void tarjan(int u) {
        dfn[u] = low[u] = ++ts;
        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 x;
            do {
                x = stk[top--];
                in_stk[x] = false;
                id[x] = scc_cnt;
                sz[scc_cnt]++;
            } while (x != u);
        }
    }
    
    int main() {
        scanf("%d %d", &n, &m);
    
        memset(h, -1, sizeof h);
        memset(hs, -1, sizeof hs);
    
        // 0号超级源点
        //∵ 恒星的亮度最暗是 1
        //∴ 0 ~ i 有一条边权为1的边
        for (int i = 1; i <= n; i++) add(h, 0, i, 1);
    
        //求最小->求所有下界的最大->最长路 √
        for (int i = 0; i < m; i++) {
            int t, a, b;
            scanf("%d %d %d", &t, &a, &b);
            if (t == 1) // a=b
                add(h, b, a, 0), add(h, a, b, 0);
            else if (t == 2) // a < b --> b>=a+1
                add(h, a, b, 1);
            else if (t == 3) // a>=b+0
                add(h, b, a, 0);
            else if (t == 4) // a>=b+1
                add(h, b, a, 1);
            else
                add(h, a, b, 0); // b>=a+0
        }
        //强连通分量+缩点
        tarjan(0);
    
        bool flag = true;              //是不是不存在正环
        for (int u = 0; u <= n; u++) { //添加上超级源点,就是n+1个点
            for (int i = h[u]; ~i; i = ne[i]) {
                int j = e[i];
                int a = id[u], b = id[j];
                if (a == b) {
                    if (w[i] > 0) {   //在同一个强连通分量中,存在边权大于0的边
                        flag = false; //必然有正环
                        break;
                    }
                } else
                    add(hs, a, b, w[i]); //建新图
            }
            if (!flag) break; //如果存在正环,退出
        }
    
        if (!flag) //有正环,输出-1
            puts("-1");
        else {
            for (int u = scc_cnt; u; u--) { //倒序输出拓扑序
                for (int i = hs[u]; ~i; i = ne[i]) {
                    int j = e[i];
                    dist[j] = max(dist[j], dist[u] + w[i]);
                }
            }
            //因为不存在正环,而且题目保证都是路径>=0,所以强连通分量中必然路径长度都是0
            //即是一样一样的东西
            LL res = 0;
            for (int i = 1; i <= scc_cnt; i++) res += (LL)dist[i] * sz[i];
            printf("%lld\n", res);
        }
        return 0;
    }
    
  • 相关阅读:
    HDU2647(拓扑排序+反向建图)
    Android Activity之间通信
    Swift 编程语言学习0.1——Swift简单介绍
    HDU 5012 Dice (BFS)
    当向后台插入或读取JSON数据遇见回车时
    Android CTS測试Fail项改动总结(四)
    【Unity 3D】学习笔记三十五:游戏实例——摄像机切换镜头
    android蓝牙4.0(BLE)开发之ibeacon初步
    Error opening zip file or JAR manifest missing : D:play-1.2.5/framework/play-1.2.5.jar
    Codeforces Round #256 (Div. 2)——Multiplication Table
  • 原文地址:https://www.cnblogs.com/littlehb/p/16090782.html
Copyright © 2020-2023  润新知