CF-125E MST Company (单度限制最小生成树)

参考红宝书

题目链接

对除 1 号点顶点外的点集,求一次最小生成森林,对于最小生成森林的联通分量,选择最短的一条边与 1 号点相连。设此时 1 号点的度为 \(k_0\),如果 \(k_0\lt L\) 则无解 (L为1号顶点的规定度)

然后通过可行交换来增加 1 号点的度,每次尝试加入一条和 1 号点相连的边,然后删去所形成的环上面的最长边。

此题点数为 5000,对于每次交换,可以用树形DP求出所有点到 1 号点的最长边。每次选择增量最小的边去交换,直到 \(k_0\) 达到 L

在实现中的一些困难:

  1. 答案要求构成生成树的边序号,所以加边时要保留原边序号信息
  2. 树形DP要找到每个点到 根 的最长路大小,以及对应边的序号
  3. 如果一个点与根直接相连,可以把这个边序号保留下来(下面代码中用path数组保留),方便之后做替换用
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 5000 + 5;
const int M = 200010;
int head[N], ver[M], nxt[M], edge[M];
int fa[N], mark[M], best[N], cand[N];
int path[N];
int n, m, k, tot;
struct Edge{
    int x, y, z;
    int id;
    Edge(int a=0,int b=0, int c=0, int d=0):x(a),y(b),z(c),id(d){}
    bool operator <(Edge b){
        return z < b.z;
    }
}e[M];
void add(int x, int y, int z, int id){
    ver[id] = y, edge[id] = z, nxt[id] = head[x], head[x] = id;
}
void addEdge(int i){
    add(e[i].x, e[i].y, e[i].z, i*2);
    add(e[i].y, e[i].x, e[i].z, i*2+1);
}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
void dfs(int x, int fat){
    for(int i=head[x];i;i=nxt[i]){
        if(!mark[i>>1] || ver[i] == fat) continue;
        int y = ver[i];
        if(x == 1){// 从根出发的边,不能被替换
            best[y] = -inf;
        } else {
            best[y] = edge[i], cand[y] = i >> 1; // 边的序号其实是 i/2
            if(best[y] < best[x]){
                best[y] = best[x];
                cand[y] = cand[x];
            }
        }
        dfs(y, x);
    }
}
bool solve(){
    sort(e + 1, e + 1 + m);
    for(int i=1;i<=n;i++) fa[i] = i;
    for(int i=1;i<=m;i++){
        int x = e[i].x, y = e[i].y;
        if(x == 1 || find(x) == find(y)) continue;
        mark[e[i].id] = 1;
        fa[find(x)] = find(y);
    }
    //按照id排序,便于之后处理
    sort(e + 1, e + 1 + m, [](Edge a, Edge b){return a.id < b.id;});
    int component = 0;//连通块个数
    for(int i=2;i<=n;i++){
        if(find(i) == i){
            component ++;
            best[i] = inf;
        }
    }
    if(component > k) return false;
    for(int i=1;i<=m;i++){
        if(e[i].x != 1) continue;
        path[e[i].y] = i;
        int rt = find(e[i].y);
        if(e[i].z < best[rt]){
            best[rt] = e[i].z;
            cand[rt] = i;
        }
    }
    for(int i=2;i<=n;i++){
        if(find(i) != i) continue;
        if(best[i] == inf) return false;
        mark[cand[i]] = 1;
    }
    for(int i=1;i<=m;i++){
        if(mark[i]) addEdge(i);
    }
    while(component < k){
        dfs(1, 0); //树形DP
        int mx = inf, tcand = 0;
        for(int i=2;i<=n;i++){
            if(path[i] == 0 || best[i] == -inf) continue;
            if(e[path[i]].z - best[i] < mx){
                mx = e[path[i]].z - best[i];
                tcand = i;
            }
        }
        if(mx == inf) return false;
        mark[cand[tcand]] = 0;
        mark[path[tcand]] = 1;
        addEdge(path[tcand]);
        component ++;
    }
    printf("%d\n", n-1);
    for(int i=1;i<=m;i++){
        if(mark[i]) printf("%d ", i);
    }
    return true;
}
int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i=1;i<=m;i++){
        int x, y, z;scanf("%d%d%d", &x, &y, &z);
        if(x > y) swap(x, y);
        e[i] = Edge(x, y, z, i);
    }
    if(!solve()) puts("-1");
    return 0;
}

posted @ 2020-04-24 15:38  kpole  阅读(204)  评论(0编辑  收藏  举报