AcWing 368. 银河

AcWing 368. 银河

一、题目描述

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

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

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

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

输入格式
第一行给出两个整数 NM

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

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

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

若无解,则输出 1

数据范围
N100000,M100000

输入样例

5 7 
1 1 2 
2 3 2 
4 4 1 
3 4 5 
5 4 5 
2 3 5 
4 5 1 

输出样例

11

二、差分约束解法

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

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

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

最长路
dist[j]dist[t]+w[i]

T=1:A=B=>AB BA
T=2:A<B=>BA+1
T=3:AB=>AB
T=4:A>B=>AB+1
T=5:AB=>BA

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

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

差分约束代码

通过了 10/11个数据

本题数据范围
N100000,M100000

糖果 那道题一样一样的数据范围,肯定是测试数据进行了加强,导致卡掉了spfa,无论你是stack优化,slf优化,还是双端队列优化,还是什么dfs优化,一概无效。

#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 dist[N];

// tarjan求scc
int dfn[N], low[N], ts, stk[N], top, in_stk[N];
int id[N], scc_cnt, sz[N];
void tarjan(int u) {
    dfn[u] = low[u] = ++ts;
    stk[++top] = u;
    in_stk[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (in_stk[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        ++scc_cnt;
        int x;
        do {
            x = stk[top--];
            in_stk[x] = 0;
            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);

    // 求最小->求所有下界的最大->最长路 √
    while (m--) {
        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);

    for (int u = 0; u <= n; u++) { // 添加上超级源点,就是n+1个点
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            int a = id[u], b = id[v];
            if (a == b) {
                if (w[i] > 0) { // 在同一个强连通分量中,存在边权大于0的边
                    puts("-1"); // 必然有正环,最长路存在正环,原不等式组无解
                    exit(0);
                }
            } else
                add(hs, a, b, w[i]); // 建新图
        }
    }

    for (int u = scc_cnt; u; u--) // 倒序输出拓扑序
        for (int i = hs[u]; ~i; i = ne[i]) {
            int v = e[i];
            dist[v] = max(dist[v], 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;
}

四、答疑解惑

Q:为什么本题不需要去重边呢?

答:去重边也是一样可以AC掉的,不去也可以AC。道理很简单,本题只是通过边不断的求最大值,多了重边求最大值也不会使得最大值变小或变大,无所谓,要是你有路径条数这样的问题,肯定就需要去重边了。

附:去重边代码

#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 dist[N];

// tarjan求scc
int dfn[N], low[N], ts, stk[N], top, in_stk[N];
int id[N], scc_cnt, sz[N];
void tarjan(int u) {
    dfn[u] = low[u] = ++ts;
    stk[++top] = u;
    in_stk[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (in_stk[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        ++scc_cnt;
        int x;
        do {
            x = stk[top--];
            in_stk[x] = 0;
            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);

    // 求最小->求所有下界的最大->最长路 √
    while (m--) {
        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);

    unordered_set<LL> S;           // 对连着两个不同强连通分量的边进行判重
    for (int u = 0; u <= n; u++) { // 添加上超级源点,就是n+1个点
        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            int a = id[u], b = id[v];

            LL hash = a * 1000000ll + b; // 新两点a,b之间的Hash值,防止出现重边
            if (a == b) {
                if (w[i] > 0) { // 在同一个强连通分量中,存在边权大于0的边
                    puts("-1"); // 必然有正环,最长路存在正环,原不等式组无解
                    exit(0);
                }
            } else if (!S.count(hash))
                add(hs, a, b, w[i]); // 建新图
        }
    }

    for (int u = scc_cnt; u; u--) // 倒序输出拓扑序
        for (int i = hs[u]; ~i; i = ne[i]) {
            int v = e[i];
            dist[v] = max(dist[v], 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;
}
posted @   糖豆爸爸  阅读(115)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-04-02 MySQL This function has none of DETERMINISTIC, NO SQL...错误1418 的原因分析及解决方法 (转)
2013-04-02 8、单机运行环境搭建之 --CentOS-6.4安装vsftpd
2013-04-02 7、单机运行环境搭建之 --CentOS-6.4安装Memcached
2013-04-02 6、单机运行环境搭建之 --CentOS-6.4安装MySQL 5.6.10并修改MySQL的root用户密码
2013-04-02 5、单机运行环境搭建之 --CentOS-6.4拷贝Tomcat7形成集群
2013-04-02 4、单机运行环境搭建之 --CentOS-6.5优化Tomcat7
2013-04-02 3、单机运行环境搭建之 --CentOS-6.5安装配置Tengine
Live2D
点击右上角即可分享
微信分享提示