题意:
有N个点,M条边,要生成一颗树。使得边权最小,而且对于起点 1 有度限制(k)。如果可以,输出所选边,否则输出 -1.
这道题可以用度限制最小生成树来做,但是这道题和度限制最小生成树又有区别,这里要求根节点的度必须为K。而后者要求小于等于K的最小值。
所以这道题目可以采用二分来实现。
采用krusical来做最小生成树的时候,每次挑选最小可行边加入集合,所以可以预处理一下与根节点相连的边,变为 w[i] ' = mid + w[i].
通过二分mid,每次求最小生成树的时候记录与1结点相连的边数。
/* ID: [email protected] PROG: LANG: C++ */ #include<map> #include<set> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<vector> #include<string> #include<fstream> #include<cstring> #include<ctype.h> #include<iostream> #include<algorithm> #define maxn 5500 #define maxm 100100 #define INF (1<<30) #define PI acos(-1.0) #define mem(a, b) memset(a, b, sizeof(a)) #define For(i, n) for (int i = 0; i < n; i++) typedef long long ll; using namespace std; int n, m, k, x[maxm], y[maxm], w[maxm], p[maxm], f[maxn]; int cnt, ans[maxn], inx; double l, r, mid; bool inline cmp(int i, int j) { return (x[i] == 1) * mid + w[i] < (x[j] == 1) * mid + w[j]; } int findroot(int x) { return f[x] = (f[x] == x ? f[x] : findroot(f[x])); } void work(bool flag) { cnt = inx = 0; for (int i = 1; i <= n; i++) f[i] = i; sort(p + 1, p + m + 1, cmp); for (int i = 1; i <= m; i++) { int j = p[i]; int u = findroot(x[j]), v = findroot(y[j]); if (u != v && (cnt + (x[j] == 1) <= k || flag)) { f[u] = v; ans[inx++] = j; if (x[j] == 1) cnt++; } } } int main () { scanf("%d%d%d", &n, &m, &k); int tot = 0; for (int i = 1; i <= m; i++) { scanf("%d%d%d", x + i, y + i, w + i); p[i] = i; if (x[i] > y[i]) swap(x[i], y[i]); if (x[i] == 1) tot++; } //如果根节点的度数小于k,或者结点数大于1,而k == 0 一定不行 if (tot < k || (n > 1 && k == 0)) { puts("-1"); return 0; } //看能否生成一棵树 mid = 0; work(1); if (inx + 1 < n) { puts("-1"); return 0; } l = -1e5, r = 1e5; while(l + 1e-5 < r && cnt != k) { mid = (l + r) / 2; work(1); if (cnt < k) r = mid; else l = mid; } if (cnt != k) mid = (l + r) / 2; work(0); printf("%d\n", inx); for (int i = 0; i < inx - 1; i++) printf("%d ", ans[i]); if (inx) printf("%d\n", ans[inx - 1]); }