P5633 最小度限制生成树
P5633 最小度限制生成树
不妨设
问题描述:给定
该问题有
先求出不含点
证明:设
e = (u, v, w) 为非树边,根据最小生成树的基本性质,存在连接u, v 的树边构成的路径P ,且路径上每条边的权值不大于w 。假设
e 在最小k 度生成树上,断开e ,u, v 不连通。因此P 上存在一条边使得其两侧不连通,加入该树边即可。\square
设
因此,在求最小
设
结论:对于
2\leq k \leq |T| 和任意T_{k - 1} ,存在T_k 为T_{k - 1} 删去一条边,且f_{k}(T) 关于k 在[1, |T|] 上下凸。证明:当
|T| = 1 或2 时显然成立。若
|T| > 2 ,根据上述结论,设使得删去后新的权值最小的边为e (不一定是权值最小的边),可钦定T_{2\sim |T|} 一定删去e 。设删去
e 后得到的两棵树为T' 和T'' ,归纳假设T' 和T'' 满足结论。因为
T 和T'' 的贡献独立,所以f_k(T) 等于f_{k_1}(T') + f_{k_2}(T'') 的最小值,其中1\leq k_1 \leq |T'| ,1\leq k_2\leq |T''| ,且k_1 + k_2 = k 。这是下凸函数的\min + 卷积,根据经典理论(闵可夫斯基和),f_k 也是下凸的。而f_{k_1}(T') 和f_{k_2}(T'') 的每个差分值都对应 “删去一条边产生的贡献”,所以f_k(T) 的每个差分值都对应 “从T' 或T'' 中删去一条边产生的贡献”。另一种理解方式:结论实际上说明了在
T_1\to T_2\to \cdots \to T_{|T|} 的过程中,每次删去一条使得新的权值最小的边,且权值变化量(每条边的代价)随着删边而不降(下凸即二阶导非负,f_{k + 1}(T) - f_{k}(T)\geq f_k(T) - f_{k - 1}(T) )。现在T 分裂成T' 和T'' ,因为T' 和T'' 独立,所以删去使得新的权值最小的边相当于初始令k_1, k_2 = 1 ,每次选择f_{k_1 + 1}(T') - f_{k_1}(T') 和f_{k_2 + 1}(T'') - f_{k_2} (T'') 较大的那个差分值,令其为f_{k + 1}(T) - f_k(T) ,然后将k 和被选中的k_i 加上1 。此时只需证明
f_2(T) - f_1(T) = f(e)\leq f_3(T) - f_2(T) 。可以证明在子图上删去一条边的代价不小于在原图上删去这条边的代价:设在原图删去
e' 之后,不含x 的连通块为C ,新增点权为C 的最小点权w(C) 。在子图删去e' 之后,一定有一侧连通块C' 包含于C 。新增点权为两侧连通块最小点权的较大值,不小于C' 的最小点权w(C') 。而C'\subseteq C ,所以w(C) \leq w(C') 。结合
f(e) 的最小性,得证。\square
上述结论告诉我们,随着
至此,存在
若删去
设删去
注意到对于
设
- 如果
y'\in C_y ,则x', y' 路径上的最大权边为e ,得e' = e ,推出y' = y_{\min} 。 - 如果
y'\in C_x ,则w(e') < w(e) (e 是C 的最大边权),根据w(y') - w(e') 的最小性得w(y') < w(y_{\min}) 。新的x 有可能变为y' ,但无论何种情况点权均不大于y_{\min} 。
因此
在 Kruskal 的过程中维护连通块的最小点权。用
最后处理遗留的细节:之前钦定了
算法在
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using pdi = pair<double, int>;
using ull = unsigned long long;
#define ppc(x) __builtin_popcount(x)
#define clz(x) __builtin_clz(x)
bool Mbe;
// mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
mt19937 rnd(1064);
int rd(int l, int r) {return rnd() % (r - l + 1) + l;}
constexpr int mod = 1e9 + 7;
void addt(int &x, int y) {x += y, x >= mod && (x -= mod);}
int add(int x, int y) {return x += y, x >= mod && (x -= mod), x;}
int ksm(int a, int b) {
int s = 1;
while(b) {
if(b & 1) s = 1ll * s * a % mod;
a = 1ll * a * a % mod, b >>= 1;
}
return s;
}
constexpr int Z = 1e6 + 5;
int fc[Z], ifc[Z];
int bin(int n, int m) {
if(n < m) return 0;
return 1ll * fc[n] * ifc[m] % mod * ifc[n - m] % mod;
}
void init_fac(int Z) {
for(int i = fc[0] = 1; i < Z; i++) fc[i] = 1ll * fc[i - 1] * i % mod;
ifc[Z - 1] = ksm(fc[Z - 1], mod - 2);
for(int i = Z - 2; ~i; i--) ifc[i] = 1ll * ifc[i + 1] * (i + 1) % mod;
}
char buf[1 << 20], *p1 = buf, *p2 = buf;
#define getc() (p1 == p2 && (p2 = (p1 = buf) + \
fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++)
inline int read() {
int x = 0;
char s = getc();
while(!isdigit(s)) s = getc();
while(isdigit(s)) x = x * 10 + s - '0', s = getc();
return x;
}
// ---------- templates above ----------
constexpr int N = 5e5 + 5;
int n, m, s, k, val[N], fa[N];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
struct edge {
int u, v, w;
bool operator < (const edge &z) const {
return w < z.w;
}
} e[N];
void mian() {
cin >> n >> m >> s >> k;
memset(val, 0x3f, N << 2);
int cnt = 0, ans = 0;
for(int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
if(u == s) val[v] = min(val[v], w);
else if(v == s) val[u] = min(val[u], w);
else e[++cnt] = {u, v, w};
}
sort(e + 1, e + cnt + 1);
for(int i = 1; i <= n; i++) fa[i] = i;
vector<int> dt;
for(int i = 1; i <= cnt; i++) {
int u = find(e[i].u);
int v = find(e[i].v);
if(u == v) continue;
if(val[u] > val[v]) swap(u, v);
fa[v] = u, ans += e[i].w;
if(val[v] < 1e9) dt.push_back(val[v] - e[i].w);
}
int deg = 0;
for(int i = 1; i <= n; i++) {
if(fa[i] != i || i == s) continue;
if(val[i] > 1e9) cout << "Impossible\n", exit(0);
deg++, ans += val[i];
}
if(deg > k || deg + dt.size() < k) cout << "Impossible\n", exit(0);
sort(dt.begin(), dt.end());
for(int i = 0; i < k - deg; i++) ans += dt[i];
cout << ans << "\n";
}
bool Med;
int main() {
fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#ifdef ALEX_WEI
FILE* IN = freopen("1.in", "r", stdin);
FILE* OUT = freopen("1.out", "w", stdout);
#endif
int T = 1;
while(T--) mian();
fprintf(stderr, "%d ms\n", int(1e3 * clock() / CLOCKS_PER_SEC));
return 0;
}
/*
g++ a.cpp -o a -std=c++14 -O2 -DALEX_WEI
*/