CF125E MST Company

题面

有一张 \(n\) 个点,\(m\) 条边的图,每条边有边权。需要找出一棵生成树,使得 1 号点度数恰好为 \(k\) ,在满足这个条件的前提下生成树的权值和尽量小。

无解输出 −1,否则任意输出一种方案即可

\(1 ≤ n ≤ 5000,0 ≤ m ≤ 100000,0 ≤ k ≤ 5000\)

solution

收获蛮大的一个题

两种做法,但其中一种好像假掉了

  • 破圈

很经典的一种套路,和 std 跑了一上午对拍,为啥我的还是过不了

思路是这样的:

首先把与一号点所连的边全部都删掉,用其他边生成一个森林。

如果生成的联通块的数目 \(x > k\) 显然无解

如果联通块数目 \(x = k\) ,把与一号点相连的边从小到大排个序,然后用它们把所有联通块连起来,形成的树就是答案

如果联通块的数目 \(x < k\) ,同上,先生成一棵树,此时 \(1\) 的度数小于 \(k\) ,考虑与一号点相连的非树边,每次向树中加一条这样的边就会形成一个环,找出环上边权最大的一条边 (不与一号点相连) 删掉,这样 1 的度数就会 + 1,重复这个操作 \(k - x\) 就会保证 1 的度数恰好为 k 了

这个做法细节蛮多的:

怎么找环上最大的边:\(DFS\)​ ,边权转到点上处理

时间复杂度:

\(O (nk \times mlogm)\)​

/*
work by:Ariel_
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int M = 1e5 + 5;
const int N = 5000 + 5;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
vector<int> T, vec;
int n, m, k, fa[N], cnt, sum, id[M << 1], del[M << 1], fag[M];
ll mx[N];
int find(int x) {return fa[x] == x ? fa[x] : find(fa[x]);}
struct Edge{int u, v, w, id;}E[M];
struct edge{int u, v, w, nxt;}e[M << 1];
int Ecnt, head[N];
void add_edge(int u, int v, int w) {
    e[++Ecnt] = (edge) {u, v, w, head[u]};
    head[u] = Ecnt;
}
bool cmp(Edge x, Edge y) {return x.w < y.w;}
void forest() {
	for (int i = 1; i <= n; i++) fa[i] = i;
	sort(E + 1, E + m + 1, cmp);
    for (int i = 1; i <= m; i++) {//生成森林 
        int x = find(E[i].u), y = find(E[i].v);
        if (x == y || x == 1 || y == 1) continue; 
        fa[x] = y; T.push_back(E[i].id);//把树边存起来 
        add_edge(E[i].u, E[i].v, E[i].w), add_edge(E[i].v, E[i].u, E[i].w);
        id[Ecnt] = id[Ecnt - 1] = E[i].id;//记录每条边的标号 
    }	
}
void find_max(int x, int f) { 
   for (int i = head[x]; i; i = e[i].nxt) {
    	int v = e[i].v;
    	if (v == f || del[i] || v == 1) continue;
    	   if (e[i].w >= e[mx[x]].w) mx[v] = i;//存的是最大边在图中的编号 
    	   else mx[v] = mx[x];
    	   find_max(v,  x);
	}
}
main(void){
	freopen("a.in", "r", stdin);
    n = read(), m = read(), k = read();
    for (int i = 1; i <= m; i++)
    E[i].u = read(), E[i].v = read(), E[i].w = read(), E[i].id = i;
	forest();
    for (int i = 1; i <= m; i++) {
      if (E[i].u != 1 && E[i].v != 1) continue;
      int u = find(E[i].u), v = find(E[i].v);
      if (u == v) vec.push_back(i);//将 1 号点连出的非树边存起来 
      else fa[u] = v, add_edge(E[i].u, E[i].v, E[i].w), T.push_back(E[i].id), k--;
	}
	
	for (int i = 2; i <= n; i++) if (find(i) != find(i - 1)){printf("-1");return 0;}	    
	
	if (k < 0){printf("-1");return 0;} // 1 号点连出的边一定会大于 k 
	
	for (int i = 1; i <= k; i++) {
	 	if (!vec.size()){ printf("-1");return 0;} 
		memset(mx, 0, sizeof mx);
		for (int j = head[1]; j; j = e[i].nxt) {
		    int v = e[i].v;
			find_max(v, 1);
		} 
		int now = -1, ans = 0x3f3f3f3f3f3f;
	    for (int j = vec.size() - 1; j >= 0; j--)  {
	       int p = vec[j], var = max(E[p].u, E[p].v);//枚举的一条非树边进行交换 
	       if (E[p].w - e[mx[var]].w < ans) ans = E[p].w - e[mx[var]].w, now = j; 
		}
		int p = vec[now], var = E[p].u == 1 ? E[p].v:E[p].u;
		add_edge(E[p].u, E[p].v, E[p].w), add_edge(E[p].v, E[p].u, E[p].w);
		T.push_back(E[p].id);
		fag[id[mx[var]]] = 1, del[mx[var]] = del[mx[var] ^ 1] = 1;
		vec.erase(vec.begin() + now);
	} 
	printf("%d\n", n - 1);
    for (int i = T.size() - 1; i >= 0; i--) if(!fag[T[i]]) printf("%d ", T[i]); 
    return 0;
}

还有一种方法:

另一种方法:考虑将 1 周围所有边权值 +delta,使得权值修正后的图存在点 1 度数为 k 的最小生成树 Delta 可以二分

上一篇:最短路/次短路/K短路


下一篇:力扣 4. 寻找两个正序数组的中位数 二分