https://acm.hdu.edu.cn/showproblem.php?pid=6982
题意:
n个城市要修n-1条道路使他们联通,有m条道路可以修,价格有原价和折扣价
问最多可以选k个折扣价时的最小花费
对于k∈[0,n-1]依次回答
做这道题,得先会做“边有黑白两色,求恰好有k条白边的最小生成树”
可以看这里https://www.cnblogs.com/TheRoadToTheGold/p/15131583.html
这上面的基础上,题目转化为
原价就是黑边,折扣价就是白边
对于k∈[0,n-1],求“边有黑白两色,求恰好有k条白边的最小生成树”
直接枚举k用所有边求最小生成树会超时
要2个优化
优化一:
因为边权<=1000,提前对所有加边权的方式求解,记录使用的白边条数和总权值
这样二分的时候直接用即可
做最小生成树的次数为2000次
不提前求解的话,做最小生成树的次数为n*log(2000) 大约为10000次
优化二:
边数太多了
结论:在黑白两种边的最小生成树中的边,一定属于只有白边的最小生成树或者只有黑边的最小生成树
这样可以把边数优化到与n同级
因为
假设存在一条 黑白两种边的最小生成树中的边e,既不属于只有白边的最小生成树又不属于只有黑边的最小生成树
把这条边e去掉,会把树分为两部分
再去对应e的颜色中的最小生成树上,用连接这两部分的边E替换e
那么含E的最小生成树权值一定<=含e的最小生成树权值
#include<bits/stdc++.h> using namespace std; #define N 1001 #define M 400001 int n,m,mm; struct node { int u,v,w,c; }f[M],e[N<<1]; int use,sum; int fa[N]; int fuse[N*2+10],fsum[N*2+10]; bool cmp(node p,node q) { if(p.w!=q.w) return p.w<q.w; return p.c<q.c; } int find(int i) { return fa[i]==i ? i : fa[i]=find(fa[i]); } void check(int x) { for(int i=1;i<=m;++i) if(!e[i].c) e[i].w+=x; sort(e+1,e+m+1,cmp); for(int i=1;i<=n;++i) fa[i]=i; int now=0,fu,fv,j=1; use=sum=0; while(now!=n-1) { fu=find(e[j].u); fv=find(e[j].v); if(fu!=fv) { now++; fa[fu]=fv; if(!e[j].c) ++use; sum+=e[j].w; } ++j; } for(int i=1;i<=m;++i) if(!e[i].c) e[i].w-=x; } void pre_edge(int col) { int now=0,j=1,fu,fv; for(int i=1;i<=n;++i) fa[i]=i; sort(f+1,f+m+1,cmp); while(now!=n-1) { if(f[j].c==col) { fu=find(f[j].u); fv=find(f[j].v); if(fu!=fv) { now++; fa[fu]=fv; e[++mm]=f[j]; } } ++j; } } int main() { int T; int l,r,mid,ans; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { scanf("%d%d%d",&f[i*2-1].u,&f[i*2-1].v,&f[i*2-1].w); f[i*2-1].c=1; f[i*2].u=f[i*2-1].u; f[i*2].v=f[i*2-1].v; scanf("%d",&f[i*2].w); f[i*2].c=0; } m<<=1; mm=0; pre_edge(1); pre_edge(0); m=mm; for(int k=-1001;k<=1001;++k) { check(k); fsum[k+1001]=sum; fuse[k+1001]=use; } for(int k=0;k<n;++k) { l=-1001; r=1001; while(l<=r) { mid=l+r>>1; if(fuse[mid+1001]>=k) { ans=fsum[mid+1001]-k*mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans); } } }