最小度限制生成树
给定图G,求使得图中结点\(V_0\)的度不大于k的最小生成树。
设与v0相连的边集合是E0,|E0|=m,首先把v0去掉,对分裂后的每一个联通块都跑一遍最小生成树,得到边集合E1。一定存在一个方案,使得G的最小任意度限制生成树只用到E1中的和E0中的元素。证明在黑书p301,关键在于一定有一条在最小生成树上的路径连接不属于E1的边(i, j),并且这个路径中的所有边都小于(i, j)。
所以现在只需要考虑变成树后的图了。设已知最小i-1度生成树,一定可以通过加入一条边,删去形成的环上的另一条边,来变成最小i度生成树(因为至少要选一条和v0相连的边)。暴力找最优的操作,时间复杂度是\(O(n^2)\)。如果每次维护任意点到v0的路径上权值最大的边,那么找最优的边的操作和维护的操作时间复杂度都是\(O(n)\)。
注意求最值及其位置的方法有两个。一是只保存位置,缺陷是初始值不好确认。而是同时保存最值和位置,但是较烦。
#include <map>
#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL maxn=2005, maxm=4e5+5;
LL n, m, k, root, tota, totb, ans;
struct Edge1{
LL x, y, d;
void set(LL a, LL b, LL c){ x=a; y=b; d=c; }
}a[maxm], b[maxm]; //以上关于前向星
bool cmp(const Edge1 &a, const Edge1 &b){ return a.d<b.d; }
//string stmp, stmp2;
//map<string, LL> ma; //以上关于离散化
LL fa[maxn];
LL find(LL x){ return fa[x]==x?x:fa[x]=find(fa[x]); } //以上关于并查集
LL fir[maxn], cnte;
struct Edge{
LL to, next, v;
}edge[maxm];
void adde(LL x, LL y, LL z){
Edge &e=edge[++cnte];
e.to=y; e.next=fir[x]; e.v=z; fir[x]=cnte;
} //以上邻接表
LL G[maxn][maxn], use[maxn]; //G:哪个边被ban掉了
LL pos1[maxn], pos2[maxn]; //路径上最大边的两个端点
LL Max[maxn]; //接上:没有必要记录边的编号,但用邻接矩阵却太浪费
void dfs(LL u, LL par){ LL v;
for (LL i=fir[u]; i; i=edge[i].next){
v=edge[i].to; if (v==par||G[u][v]) continue;
Max[v]=max(Max[u], edge[i].v);
if (edge[i].v>Max[u]) pos1[v]=v, pos2[v]=u;
else pos1[v]=pos1[u], pos2[v]=pos2[u];
dfs(v, u);
}
}
int main(){
scanf("%lld%lld%lld", &n, &m, &k); LL t1, t2, v;
for (LL i=0; i<m; ++i){
scanf("%lld%lld%lld", &t1, &t2, &v);
if (t1&&t2) a[tota++].set(t1, t2, v);
else b[totb++].set(t1, t2, v);
}
//去掉root,kruskal重建图
sort(a, a+tota, cmp); sort(b, b+totb, cmp);
for (LL i=1; i<=n; ++i) fa[i]=i;
for (LL i=0; i<tota; ++i){
if (find(a[i].x)==find(a[i].y)) continue;
fa[find(a[i].x)]=find(a[i].y); ans+=a[i].d;
adde(a[i].x, a[i].y, a[i].d); adde(a[i].y, a[i].x, a[i].d);
}
//加入与root相连的最小边
for (LL i=0; i<totb; ++i){
if (find(b[i].x)==find(b[i].y)) continue;
fa[find(b[i].x)]=find(b[i].y); ans+=b[i].d;
--k; use[i]=true;
adde(b[i].x, b[i].y, b[i].d); adde(b[i].y, b[i].x, b[i].d);
}
while (k--){ //加几条边
dfs(root, 0);
LL tmp, maxm=0, maxp;
for (LL i=0; i<totb; ++i){
if (use[i]) continue;
tmp=max(Max[b[i].x], Max[b[i].y]); tmp-=b[i].d;
if (maxm<tmp) maxm=tmp, maxp=i;
} //找到最好的边
if (!maxm) break;
if (b[maxp].x) swap(b[maxp].x, b[maxp].y);
G[pos1[b[maxp].y]][pos2[b[maxp].y]]=true;
G[pos2[b[maxp].y]][pos1[b[maxp].y]]=true;
adde(b[maxp].x, b[maxp].y, b[maxp].d);
adde(b[maxp].y, b[maxp].x, b[maxp].d);
ans=ans-maxm; use[maxp]=true;
}
printf("%lld\n", ans);
return 0;
}