求助:Sub2过了,Sub1没过,(附带注释)。
查看原帖
求助:Sub2过了,Sub1没过,(附带注释)。
244309
yuhaocheng楼主2022/8/4 21:20

RT,代码如下,已经尽可能地写好注释了:

#include <bits/stdc++.h>
using namespace std;

#define DEBUG 0

#define int long long

#if DEBUG == 0
#define MAXN 50005
#define MAXM 500005
#else
#define MAXN 2005
#define MAXM 2005
#endif

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

struct ToNode {
    int t, w;
};

int n, m, s, k;
Edge e[MAXM];
int fa[MAXN];
bool ac[MAXM];
vector<ToNode> g[MAXN];
bool vis[MAXN];

void init(int x) { // 并查集 - 初始化
    for(int i = 1; i <= x; i++) {
        fa[i] = i;
    }
}

int find(int x) { // 并查集 - 查找
    if(fa[x] == x) {
        return x;
    } else {
        return fa[x] = find(fa[x]);
    }
}

void merge(int x, int y) { // 并查集 - 合并
    fa[find(x)] = find(y);
}

void dfs(int x) { // 将x所在的连通块中的每一个节点都标记为「已访问」
    vis[x] = 1;
    for(ToNode &i : g[x]) {
        if(vis[i.t]) {
            continue;
        }
        dfs(i.t);
    }
}

bool cmpE(const Edge &_a, const Edge &_b) { // 用于Edge的比较函数
    if((_a.u == s) != (_b.u == s)) { // 如果有一条边是从s发出的
        return (_a.u == s) > (_b.u == s); // 那么这条边靠前排
    } else { // 其他情况
        return _a.w < _b.w; // 按照边权排序
    }
}

bool cmpTN(const ToNode &_a, const ToNode &_b) { // 用于ToNode的比较函数
    return _a.w < _b.w;
}

signed main() {
    scanf("%lld%lld%lld%lld", &n, &m, &s, &k);
    for(int i = 1; i <= m; i++) {
        scanf("%lld%lld%lld", &e[i].u, &e[i].v, &e[i].w);
        if(e[i].v == s) { 
            swap(e[i].u, e[i].v); // 调整顺序,使得s节点总是为起始点
        }
        if(e[i].u != s) {
            g[e[i].u].push_back({e[i].v, e[i].w});
            g[e[i].v].push_back({e[i].u, e[i].w}); // 记录双向边
        } else {
            g[e[i].u].push_back({e[i].v, e[i].w}); // 只添加从s出发的边,防止下面dfs的时候遍历到节点s
        }
    }
    init(n); // 初始化并查集
    int cnts = 0; // cnts记录当前已经添加的「从s发出的边数」
    int cnt = 0; // cnt记录当前已经添加的「总边数」
    int ans = 0;
    sort(g[s].begin(), g[s].end(), cmpTN); // 对从s发出的所有边按边的权值排序
    for(ToNode &i : g[s]) { // 遍历从s出发的边
        if(!vis[i.t]) { // 所在连通块没有被标记过
            dfs(i.t); // 标记整个连通块
            cnt++;
            cnts++;
            ans += i.w; // 记录答案
            merge(1, i.t); // 添加这条边
        }
    }
    sort(e + 1, e + m + 1, cmpE); // 对剩余的边排序
    for(int i = 1; i <= m; i++) { // Kruskal
        if(find(e[i].u) != find(e[i].v)) {
            if(e[i].u == s && cnts >= k) { // 如果这条边是从s发出且从s节点发出的边已经足够了
                continue; // 那么忽略这条边
            }
            merge(e[i].u, e[i].v);
            cnt++;
            ans += e[i].w; // 记录答案
            if(e[i].u == s) { // 若这条边是从s发出的,则记录
                cnts++;
            }
        }
    }
    if(cnt < n - 1 || cnts != k) { // 不是一棵树 或者 从s发出的边不足k条
        puts("Impossible"); // It's IMPOSSIBLE!
    } else {
        printf("%lld\n", ans); // 答案
    }
}
2022/8/4 21:20
加载中...