大意: 无向图, 其中k条边是你的, 边权待定, m条边是你对手的, 边权已知. 求如何设置边权能使最小生成树中, 你的边全被选到, 且你的边的边权和最大. 若有多棵最小生成树优先取你的边.
先将$k$条边合并, 然后按边权从小到大添对手的边, 若连通, 则树链取最小值, 否则合并一下.
正确性其实很显然.
然后对于树链取最小有多种方法, 强制在线可以树剖$O(nlog^2n)$, 可以离线的话可以用倍增$O(nlogn)$, 或者并查集$O(n)$.
#include <iostream> #include <iostream> #include <algorithm> #include <cstdio> #include <math.h> #include <set> #include <map> #include <queue> #include <string> #include <string.h> #include <bitset> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl ' ' #define DB(a) ({REP(__i,1,n) cout<<a[__i]<<' ';hr;}) using namespace std; typedef long long ll; const int N = 1e6+10; int n, k, m, cnt, fa[N]; struct _ {int to,id;} pre[N]; vector<_> g[N]; struct {int u,v,w;} e[N]; int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;} int dep[N], ans[N]; void dfs(int x, int d, int f) { dep[x] = d; for (_ e:g[x]) if (e.to!=f) { pre[e.to] = {x,e.id}; dfs(e.to,d+1,x); } } int main() { scanf("%d%d%d", &n, &k, &m); int tot = 0; while (k--) { int u, v; scanf("%d%d", &u, &v); fa[Find(u)] = Find(v); g[u].pb({v,1}),g[v].pb({u,1}); } REP(i,1,m) { int u, v, w; scanf("%d%d%d", &u, &v, &w); int uu = Find(u), vv = Find(v); if (uu==vv) e[++cnt]={u,v,w}; else { fa[uu] = vv; g[u].pb({v,0}), g[v].pb({u,0}); } } dfs(1,0,0); memset(fa,0,sizeof fa); REP(i,1,cnt) { int u = e[i].u, v = e[i].v, w = e[i].w; while (Find(u)!=Find(v)) { if (dep[u]<dep[v]) swap(u,v); if (Find(u)!=Find(pre[u].to)) { fa[u] = Find(pre[u].to); ans[u] = w; } u = Find(u); } } ll sum = 0; REP(i,1,n) if (pre[i].to&&pre[i].id) { if (!ans[i]) return puts("-1"),0; else sum += ans[i]; } printf("%lld ", sum); }