有一张 (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 imes 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 - 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 可以二分