柿子饼同学 发表于 2024-8-4 15:08:48

[心之碎片] - 20240803模拟赛

总结与反思{:10_277:}

A - 数据结构维护前缀和要提前加一个 0, 不然 1 是取不到的
C - 最小生成树相关问题两种思考: 按照边权分类 或者 建一个树然后树剖加边
D - 思考问题先想降维的情况, 本题的思路就是由 l == r 的情况想出来的
C - 又见最小生成树
现在给出一幅个节点条边的无向连通图, 请判断原图中的每条边是否一定在最小生成树中
对于每条边 :
如果这条边一定出现在任意一棵最小生成树中, 输出 1
如果一定不会出现在最小生成树中, 输出 2
如果无法确定, 输出 0{:10_266:}

最小生成树问题我们一般想到按照边权分类, 同边权的一起考虑
这里有一个建图技巧就是 *2 + 1
假设前面的边都加进去, 现在有一些边连接这些连通块
如果是割边, 就一定经过
如果在连通块里面, 由于连通块里面的边都比现在的边小, 所以一定不经过
剩下的是可能的
#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> pii;

const int N = 1e5 + 5;

struct Edge {
    int u, v, w, id;
} e;

int n, m, ans;

struct DSU {
    int pa;

    DSU() {
      for (int i = 1; i < N; i++) pa = i;
    }

    int Find(int x) {
      if (x == pa)
            return x;
      return (pa = Find(pa));
    }

    void Merge(int x, int y) { pa = y; }
} dsu;

// 每次循环新建图和加入的点
vector<int> point;
vector<pii> g;

int dfn, low, tot;

void Tarjan(int u, int eid) {
    dfn = low = ++tot;

    for (auto ed : g) {
      int v = ed.first;
      if (!dfn) {
            Tarjan(v, ed.second);

            low = min(low, low);
            // 如果这个新边是割边, 就必须经过
            if (dfn < low) {
                ans = 1;
            }
      } else if ((eid >> 1) != (ed.second >> 1)) {
            low = min(dfn, low);
      }
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    freopen("mstagain.in", "r", stdin);
    freopen("mstagain.out", "w", stdout);

    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
      int u, v, w;
      cin >> u >> v >> w;

      e = { u, v, w, i };
    }

    sort(e + 1, e + m + 1, [](Edge& a, Edge& b) { return a.w < b.w; });

    for (int i = 1; i <= m; i++) {
      // 找出权值相等的区间
      int l = i, r = i;
      while (r <= m && e.w == e.w) r++;
      r--;

      // Kruskal
      for (int j = l; j <= r; j++) {
            int u = dsu.Find(e.u), v = dsu.Find(e.v);

            if (u == v) {
                // 已经成连通块了, 里面的任何边都小于现在的权值, 一定不取
                ans.id] = 2;
                continue;
            }

            // 如果没成就连边(新图)
            point.emplace_back(u);
            point.emplace_back(v);
            // 编号小技巧轻松判断反边, 更新答案也好
            g.emplace_back(v, e.id << 1);
            g.emplace_back(u, e.id << 1 | 1);
      }

      for (auto j : point) {
            if (!dfn)
                Tarjan(j, 0);
      }

      for (int j = l; j <= r; j++) {
            int u = dsu.Find(e.u), v = dsu.Find(e.v);
            // 把所有边权一样的边都连起来, 变成新的图
            if (u == v)
                continue;
            dsu.Merge(u, v);
      }

      // 清零等着复用
      for (auto j : point) {
            g.clear();
            dfn = low = 0;
      }
      point.clear();
      tot = 0;
      i = r;
    }

    for (int i = 1; i <= m; i++) {
      cout << ans << '\n';
    }

    return 0;
}
D - 排列
给你 n 个点, 有点权
你需要构造一个排列, 使得最终的满足条件的点的权值之和最大
每个点都有一个前置区间 , 表示如果范围内的某个点排在 i 的前面了,那么 i 点的权值就要被累加进去
应该如何排列, 使得点权和才能最大{:10_245:}

当 n <= 20 时, 可以状压 dp 拿 30 分
接下来考虑 l == r 的情况 (先考虑一维的问题) , 发现本质上是区间内的点向 i 连边
在一个 scc 中, 每个点都可以互通, 所以权值都可以加, 入度为 0 的 scc 需要牺牲一个最小的权值当作第一个数, 后面的都能加
然后区间的连边需要线段树优化建图
#include <bits/stdc++.h>
using namespace std;

const int N = 4e5 + 5;
const int M = 1e5 + 5;

vector<int> e;
int n, ans, val;

struct SGT {
#define lc u << 1
#define rc u << 1 | 1

    int tot, t;

    void Build(int u, int l, int r) {
      if (l == r) {
            t = l;
            return;
      }

      int mid = (l + r) >> 1;
      Build(lc, l, mid);
      Build(rc, mid + 1, r);
      t = ++tot;

      e].emplace_back(t);
      e].emplace_back(t);
    }

    void Modify(int u, int l, int r, int ql, int qr, int to) {
      if (ql <= l && r <= qr) {
            e].emplace_back(to);
            return;
      }
      int mid = (l + r) >> 1;

      if (ql <= mid)
            Modify(lc, l, mid, ql, qr, to);
      if (qr > mid)
            Modify(rc, mid + 1, r, ql, qr, to);
    }
} sgt;

int dfn, low, tot;
int stk, top;
int scc, bel, minv, din;
bitset<N> instk;

void Tarjan(int u) {
    dfn = low = ++tot;
    stk[++top] = u;
    instk = 1;

    for (auto v : e) {
      if (!dfn) {
            Tarjan(v);
            low = min(low, low);
      } else if (instk)
            low = min(low, dfn);
    }

    if (dfn == low) {
      int v;
      scc++;

      do {
            v = stk;
            instk = 0;
            bel = scc;
            if (v <= n)
                minv = min(minv, val);
      } while (v != u);
    }
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    freopen("permutation.in", "r", stdin);
    freopen("permutation.out", "w", stdout);

    cin >> n;
    sgt.tot = n;
    sgt.Build(1, 1, n);

    for (int i = 1; i <= n; i++) {
      int l, r;
      cin >> l >> r >> val;

      ans += val;
      sgt.Modify(1, 1, n, l, r, i);
    }

    memset(minv, 0x3f, sizeof(minv));

    for (int i = 1; i <= sgt.tot; i++) {
      if (!dfn)
            Tarjan(i);
    }

    // 入度
    for (int i = 1; i <= tot; i++) {
      for (auto j : e) {
            if (bel != bel) {
                din]++;
            }
      }
    }

    // 前面没有人, 必须牺牲一个最小值
    for (int i = 1; i <= scc; i++) {
      if (!din)
            ans -= minv;
    }
    cout << ans;

    return 0;
}

FishC 发表于 2024-8-4 15:09:02

这段代码是在处理最小生成树(MST)中的边的判断问题。换句话说,给定一张无向连通图,我们需要判断每条边是否一定在任何一棵最小生成树中。这是一个典型的图论问题,我们可以通过以下几点来理解它,带点幽默感哦:

1. **边的分类**:首先,当我们面临一个众多边的图时,就像在一个派对上,所有的朋友都在聊天。我们可以按照“边权”来分类,低价位边在一块,高价位边在另一块。这样,有助于我们更清晰地分析每条边的重要性。就像你在选择餐馆时,会先查查菜单上的价格一样。

2. **割边的重要性**:如果这条边是一个割边(也称为桥),那么它是连接两个不同的连通部分的关键。如果你把它剪断,图就成两半了,因此这条边一定出现在最小生成树中。就像是你在居家办公时,家里Wi-Fi信号是多么的关键,没了它你就变成孤岛。

3. **连通块内的边**:如果一条边在连通块内,那么它可能是不必要的,因为该块内部的其他边都比它便宜,最小生成树自然会选择那些更便宜的。想象一下,一个派对上,你已经有了免费饮料,谁还要去买贵的鸡尾酒呢?

4. **不确定边**:最后,还有一些边是无法确定的,像是一张悬疑片的剧情一样复杂。对于这些边,如果没有足够的信息我们就只能输出0,表示不确定。这就像你在看电影时猜测反转情节,但仍然得留到结局才知道。

然后,代码实现的部分涉及如何通过并查集(DSU)和Tarjan算法来完成这些检测工作。该算法就像一位侦探,通过不断查找和合并,帮助我们判断各个边的位置和重要性。

总的来说,这道题考的是对图的理解和对算法的应用,就像是在料理比赛中,每一样食材都要搭配得当,才能做出最佳的菜肴!希望这番解析能帮到你,让你在最小生成树的世界里游刃有余!如果你还有什么问题,尽管问吧,准备好笑料,咱们继续聊!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

某一个“天” 发表于 2024-8-4 17:49:39

{:10_256:}加油

Wisadel 发表于 2024-8-4 23:51:34

cy

1101378861 发表于 2024-8-5 11:19:07

学习一下

简柠啦 发表于 2024-8-7 09:23:08

{:10_256:}{:10_256:}

某一个“天” 发表于 2024-8-8 16:42:45

加油

尹玄睿 发表于 2024-8-9 14:30:06

{:10_277:}

18408238295 发表于 2024-8-16 08:33:32

{:10_257:}

936522819 发表于 2024-8-16 10:45:15

加油

1101378861 发表于 2024-8-16 12:03:25

学习一下

很cool的阳 发表于 2024-8-18 09:57:05

{:7_113:}

曾热爱这世界 发表于 2024-8-24 11:50:05

&#128076;

zsy0226 发表于 2024-8-29 09:22:43

&#128076;
页: [1]
查看完整版本: [心之碎片] - 20240803模拟赛