首页 > 其他分享 >ABC270F 题解

ABC270F 题解

时间:2023-12-01 16:22:32浏览次数:36  
标签:findFa int 题解 边权 源点 pos fa ABC270F

和博客园一样好的体验

思路

首先看到花最小代价使得所有点连通,果断转换成最小生成树问题。

接下来就要考虑怎么建图,首先陆地就正常连不用说,建机场和港口的代价貌似都是点权,考虑转成边权。因为一个点飞或者划船到另一个点要两重代价,所以若我们想让 \(u\) 和 \(v\) 建能飞过去的边,我们可以先从 \(u\) 建一条边权为 \(x[u]\) 的边到 \(t\),再从 \(t\) 建一条边权为 \(x[v]\) 的边到 \(v\)。

我们按照上面的思路让每个点都和其余 \(n-1\) 个点这样建边就能得到一张完全图。这样建图虽然在意义上是没有问题的,但是再跑最小生成树时会强制性把所有无关的中转点也选进去,导致代价重复计算,而且是完全图,边数达到 \(O(n^2)\) 级别,光建图就 TLE 了。

我们可以这样去想:我们可以多建两个大点,表示一整片天空,和一整片海。每个点都要对着海和天空连一条边(听起来怎么有点浪漫),边权为自己建机场和港口的代价。这样我们就可以实现我们刚才想达到的所有目的了。

为什么呢?首先看第一个目的,为了解决点权转边权,我们也用了中转站解决,而且只要多建 \(2n\) 条边。那这样也有个问题,也不是非要走天和走海,万一走普通边最优呢。为了解决这个问题我们可以直接暴力跑 4 次最小生成树——

  1. 只开车

  2. 只飞天和开车

  3. 只航海和开车

  4. 又飞天又航海又开车

这样就完美解决了。至于最重要的一点,为什么只用所有点连到天空和海不影响图的结构?因为图的本质就是一堆点集和一堆描述联通关系的边。我们看第一版建边思路,点集只有 \(O(n)\),但边数真的有必要这么多吗?

我们注意到每一个我们建的中转点连出去的点集都是一样的,因此有一个中转点就足够。而且对于每一个点,能达到的点和边权的状态都和有一个中转点的情况相同,所以没必要连成完全图,而是把属于这一类的所有边聚集到一个源点,将稠密图稀疏化,而且使得以前每个点能到达的点与边权的状态一样……

我们一般把这类 trick 叫做 建超级源点,聚集到的源点叫做 超级源点。当然这也只是超级源点的一种用法,之后更多好玩的用法大家可以多找点图论题找找,说不定那道题的题解也会是我写的

实现

之后讲一下实现。也就是按上面的思路建 4 次边跑 4 次最短路,但有一些细节值得注意。

  • 第 0 次跑最小生成树时,陆地上的边不一定能形成最小生成树,但是其他 3 次保证一定能形成最小生成树。

  • 最多会有 \(3n\) 条边,数组要开够

  • 做 4 次生成树要建 4 次边,而有些代码可以复用,可以使用一些模拟技巧缩短此题代码长度

代码

//
//  main.cpp
//  [ABC270F] Transportation
//
//  Created by SkyWave Sun on 2023/11/30.
//

#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <climits>
using namespace std;
typedef long long ll;

const int N = 2e5 + 1;

struct dsu {
    vector<int> fa;
    
    void resize(int sz) {
        fa.resize(sz + 1);
        iota(fa.begin(), fa.end(), 0);
    }
    
    dsu() {
        
    }
    dsu(int sz) {
        resize(sz);
    }
    
    int findFa(int pos) {
        return pos == fa[pos] ? pos : fa[pos] = findFa(fa[pos]);
    }
    
    void mergeFa(int u, int v) {
        fa[findFa(u)] = findFa(v);
    }
    
    bool same(int u, int v) {
        return findFa(u) == findFa(v);
    }
};

struct Edge {
    int u, v, w;
    bool operator < (const Edge &e) const {
        return w < e.w;
    }
};

Edge e[N * 3];//注意数组大小

ll kruskal(int n, int m) {
    sort(e + 1, e + m + 1);
    dsu d(n);
    ll ans = 0;
    int cnt = 0;
    for (int i = 1; i <= m; ++i) {
        if (!d.same(e[i].u, e[i].v)) {
            d.mergeFa(e[i].u, e[i].v);
            ans += e[i].w;
            ++cnt;
        }
        if (cnt == n - 1) {
            break;
        }
    }
    return cnt == n - 1 ? ans : LONG_LONG_MAX;//不能形成最小生成树时返回无穷大不对最后答案产生影响
}

int x[N], y[N];

int u[N], v[N], w[N];

int main(int argc, const char * argv[]) {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &x[i]);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &y[i]);
    }
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d%d", &u[i], &v[i], &w[i]);
    }
    
    ll ans = LONG_LONG_MAX;
    for (int i = 0; i < 4; ++i) {//类似状压枚举复用建边代码
        int source = n/*超级源点编号*/, rear = 0;
        if (i & 1) {
            ++source;/*天上边超级源点为 n + 1*/
            for (int j = 1; j <= n; ++j) {
                e[++rear] = {source, j, x[j]};
            }
        }//在 i = 1、 3 的时候建天上边
        if (i & 2) {
            ++source;/*海上边超级源点为 n + 2*/
            for (int j = 1; j <= n; ++j) {
                e[++rear] = {source, j, y[j]};
            }
        }//在 i = 2、3 的时候建海上边
        for (int j = 1; j <= m; ++j) {
            e[++rear] = {u[j], v[j], w[j]};
        }//i = 0、1、2、3 都要建陆地边
        ans = min(ans, kruskal(source, rear));
    }
    printf("%lld\n", ans);
    return 0;
}

标签:findFa,int,题解,边权,源点,pos,fa,ABC270F
From: https://www.cnblogs.com/SkyWave20100601/p/17869977.html

相关文章

  • P6859 蝴蝶与花 题解
    题意:有一个长度为$n$的序列$a$,其中所有元素都为$1$或$2$,要求进行$q$次操作,每次操作为以下之一:$A$$s$:询问是否存在$a$的连续子序列满足其中元素总和为$s$,若有合法的方案,输出这个方案的左右端点位置(多种方案时输出左端点最小的方案),否则输出$......
  • CF1835D Doctor's Brown Hypothesis 题解
    题目链接点击打开链接题目解法首先只有在一个强联通分量里的点对才可能合法,因此我们这里说的图默认为强联通图但是上面的条件成立只需要满足\(k\gen\),考虑用好\(k\)可以认为是极大的性质所以说我们可以通过图中所有的环\(+\)路径来凑出\(k\)不难发现,所有的环能构成的......
  • CF249题解
    CF249linkCF249ElinkCF249E题意给你一个形如下图的矩阵并有\(T\)组询问每组询问给出\(x_1,y_1,x_2,y_2\)。求\(\sum_{i=x_1}^{x_2}\sum_{j=y_1}^{y_2}A[i][j]\)。其中\(A[i][j]\)表示在矩阵中的数。\(T\leq10^5\)\(1\leqx_1\leqx_2\leq10^9\)\(1\leqy_1......
  • P7110 晚秋绝诗 题解
    好有意思的题目啊。出题人太厉害了。思路考虑一个结论:我们将两个没插旗的点与中间的点称为一段,其中中间的点必须全部插旗。那么这一段如果已知两座山的高度,就一定可以得知所有的高度。考虑为什么。加入这一段是\(a\simb\)。\[\begin{cases}h_a+h_{a+2}=2\timesh_{a+1}......
  • Advent of Code 2023题解 [Mathematica/Python]
    Day1Part1(*读取文件*)lines=ReadList["E:\\ExplorerDownload\input.txt",String];(*计算校准值*)calibrationValues=ToExpression[StringJoin[#[[1]],#[[-1]]]]&/@(StringCases[#,DigitCharacter]&/@lines);(*打印总和*)Pri......
  • CF1198题解
    CF1198CodeforcesRound576(Div.1)CF1198AlinkCF1198A题意有一种数字化一段录音的常用方式,是记录每一个时刻的强度值。这些非负的强度值就可以代表一段音频对于一段音频,若有\(K\)个不同的强度值,那么每一位我们都需要\(k=\lceil\log_2K\rceil\)\(\text{bit}\)来存......
  • [AGC052B] Tree Edges XOR 题解
    题目链接点击打开链接题目解法怎么感觉这场\(B\)比\(C\)思维量更大考虑一步很妙的操作:把边权变成点权,以达到简化操作的目的使每条边的边权为两端点的异或和,手画一下可以发现,操作简化成了交换两端点的点权我们定义\(d_{1/2,i}\)定义为在\(1/2\)树上,\(i\)到根的权值......
  • CF1684题解
    CF1684CodeforcesRound792(Div.1+Div.2)CF1684AlinkCF1684A题意有一个用十进制表示的没有前导零的正整数\(n\)。Alice和Bob正在用这个数玩一个游戏。Alice先手,他们轮流进行游戏。在她的这一轮中,Alice应该交换这个数中的任何不同位置的两位。轮到Bob时,他每次......
  • [AGC052C] Nondivisible Prefix Sums 题解
    题目链接点击打开链接题目解法好题!一个序列是不合法的,必定满足某些结论,我们不妨猜测一下首先如果和为\(P\)的倍数,必定不合法然后手玩几个可以发现,最极限的情况是\(P-1\)个\(1\;+\;\)\(b_i\;+\;\)\(P-b_i\)如果在这个情况下再加一个\(1\),就爆了其中\(1\)可以替......
  • CF689题解
    CF689CodeforcesRound361(Div.2)CF689AlinkCF689A题意题目描述迈克在海滩游泳时不小心将手机放入水中。他买了一个带有老式键盘的手机。键盘只有十个数字大小的键,位于以下方式:1234567890联系人与他的旧手机一起消失了,他现在只能记住他的手指......