图论·最小生成树
最小生成树(MST)
最小生成树是无向图中边权之和最小的生成树,显然有全部
因为MST一定包含图中权值最小的边,所以可以贪心构造MST。
Kruskal算法
kruskal是对边进行贪心,每次选取权值最小的边,用并查集维护两个结点的连通性。
kruskal编码简单,复杂度为
板子代码:
bool cmp(node x,node y) { return x.w<y.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+m,cmp);
for (int i=1;i<=m;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
cnt++;
ans+=w;
fa[fa1]=fa2;
if (cnt==n-1) break;
}
}
Prim算法
Prim是对点进行贪心,思想与Dijkstra基本相同。prim之中没有dijkstra的松弛操作,每次用小根堆找出目前点集中最近的点。
复杂度为
板子代码:
priority_queue < pii,vector <pii>,greater <pii> > q;
void prim()
{
memset(vis,0,sizeof vis);
q.push(mp(0,1));
while (!q.empty())
{
pii u=q.top();
q.pop();
if (vis[u.second]) continue;
vis[u.second]=true;
sum+=u.first(),cnt++;
if (cnt==n) break;
int _size=e[u.second].size();
for (int i=0;i<_size;i++)
{
int v=e[u.second][i].nxt;
if (vis[v]) continue;
q.push(mp(e[u.second][i].val,v));
}
}
}
严格次小生成树
求出MST后,用LCA计算每两点间路径上的最大边长与次大边长,再枚举每条不在MST上的边,替换MST上原来的边即可。
长成一坨的代码
#incIude <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mp make_pair
using namespace std;
const int N=1e5+5;
const int M=3e5+5;
int n,m;
struct node{
int u,v,w;
}e[M];
struct NODE{
int nxt,val;
};
vector <NODE> tr[N];
bool flag[M];
int sum;
int ans=0x3f3f3f3f3f3f3f3f;
int fa[N];
bool cmp(node a,node b) { return a.w<b.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+m,cmp);
int cnt=0;
for (int i=1;i<=m;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
fa[fa1]=fa2;
sum+=e[i].w;
flag[i]=true;
cnt++;
tr[u].push_back({v,w}),tr[v].push_back({u,w});
if (cnt==n-1) break;
}
}
int dep[N],st[N][22],g[N][22][4];
void dfs(int x,int _fa)
{
dep[x]=dep[_fa]+1,st[x][0]=_fa;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i].nxt;
if (v==_fa) continue;
g[v][0][0]=tr[x][i].val,g[v][0][1]=0xc1c1c1c1c1c1c1c1;
dfs(v,x);
}
}
void initST()
{
for (int j=1;j<=20;j++)
{
for (int i=1;i<=n;i++)
{
st[i][j]=st[st[i][j-1]][j-1];
g[i][j][0]=max(g[i][j-1][0],g[st[i][j-1]][j-1][0]);
if (g[i][j-1][0]==g[st[i][j-1]][j-1][0]) g[i][j][1]=max(g[i][j-1][1],g[st[i][j-1]][j-1][1]);
else if (g[i][j-1][0]<g[st[i][j-1]][j-1][0]) g[i][j][1]=max(g[i][j-1][0],g[st[i][j-1]][j-1][1]);
else g[i][j][1]=max(g[i][j-1][1],g[st[i][j-1]][j-1][0]);
}
}
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
int delta=dep[x]-dep[y],lg=0;
while (delta)
{
if (delta&1) x=st[x][lg];
delta>>=1,lg++;
}
if (x==y) return x;
for (int i=20;i>=0;i--)
{
if (st[x][i]==st[y][i]) continue;
x=st[x][i],y=st[y][i];
}
return st[x][0];
}
int get(int x,int _lca,int lim)
{
int res=0xc1c1c1c1c1c1c1c1;
for (int i=20;i>=0;i--)
{
if (dep[st[x][i]]<dep[_lca]) continue;
if (lim!=g[x][i][0]) res=max(res,g[x][i][0]);
else res=max(res,g[x][i][1]);
x=st[x][i];
}
return res;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1,u,v,w;i<=m;i++) scanf("%lld%lld%lld",&e[i].u,&e[i].v,&e[i].w);
kruskal();
dfs(1,0);
initST();
for (int i=1;i<=m;i++)
{
if (flag[i]) continue;
int u=e[i].u,v=e[i].v,w=e[i].w;
int _lca=lca(u,v);
int maxu=get(u,_lca,w),maxv=get(v,_lca,w);
ans=min(ans,sum+w-max(maxu,maxv));
}
printf("%lld",ans);
return 0;
}
最优比率生成树
就是ybt的最后一道题。
这里要求的是
这就是01分数规划。一顿移项后,得
其中
于是,建一个新图,图的新边权即
【YbtOj】例题
A.繁忙都市
板子题,秒了
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=310;
const int M=1e5+5;
int n,m;
struct node{
int u,v,w;
}e[M];
int ans1,ans2;
int fa[N];
bool cmp(node a,node b) { return a.w<b.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+m,cmp);
for (int i=1;i<=m;i++)
{
if (ans1>=n-1) return ;
int u=e[i].u,v=e[i].v,w=e[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
fa[fa1]=fa2;
ans2=w,ans1++;
}
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1;i<=m;i++) scanf("%lld%lld%lld",&e[i].u,&e[i].v,&e[i].w);
kruskal();
printf("%lld %lld",ans1,ans2);
return 0;
}
B.新的开始
因为一定要有发电站,所以在这建立一个虚点,虚点与其他点的路径权值就是
建虚点的代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
int n,m;
int x,tol;
int c[N];
bool b[N];
int k;
int mn,mx=0x3f3f3f3f3f3f3f3f;
int f[N][N];
signed main()
{
scanf("%lld",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&f[i][0]);
f[0][i]=f[i][0];
c[i]=f[0][i];
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++) scanf("%lld",&f[i][j]);
f[i][i]=0x3f3f3f3f3f3f3f3f;
}
b[0]=true;
for (int i=1;i<=n;i++)
{
mn=mx,k=0;
for (int j=1;j<=n;j++) if (b[j]==0&&c[j]<mn) mn=c[j],k=j;
tol+=c[k];
b[k]=true;
for (int j=1;j<=n;j++) if (f[k][j]<c[j]) c[j]=f[k][j];
}
printf("%lld",tol);
return 0;
}
附上我想的神奇方法:将问题转化为使原图分成若干个联通块,每个联通块都有一个代价最小的发电站,求最小代价。这里用到带权并查集,维护点集
感谢sxht dalao的帮忙!
神奇做法的代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=305;
const int M=2e5;
int n;
int p[N];
struct node{
int u,v,w;
}e[M];
int cnt;
int tol;
int ans;
int fa[N];
bool cmp(node x,node y) { return x.w<y.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
int ret=find_fa(fa[x]);
p[x]=p[fa[x]];
return fa[x]=ret;
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+cnt,cmp);
for (int i=1;i<=cnt;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
if (w<max(p[u],p[v]))
{
tol++;
ans=ans-max(p[u],p[v])+w;
if (p[u]>p[v]) p[u]=p[v],fa[fa1]=fa2;
else p[v]=p[u],fa[fa2]=fa1;
}
if (tol>=n-1) break;
}
}
signed main()
{
scanf("%lld",&n);
for (int i=1;i<=n;i++)
{
scanf("%lld",&p[i]);
ans+=p[i];
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
int w;
scanf("%lld",&w);
e[++cnt]={i,j,w};
}
}
kruskal();
printf("%lld",ans);
return 0;
}
C.公路建设
注意到
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=510;
const int M=2005;
int n,m;
int fa[N];
struct node{
int u,v,w;
}e[M];
int cnt;
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
double kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=cnt;i>=1;i--) //保持升序
{
if (e[i-1].w>e[i].w) swap(e[i-1],e[i]);
}
int k=0,res=0;
for (int i=1;i<=cnt;i++)
{
int fa1=find_fa(e[i].u),fa2=find_fa(e[i].v);
if (fa1!=fa2)
{
fa[fa1]=fa2;
res+=e[i].w;
}
else k=i;
}
if (k)
{
cnt--;
for (int i=k;i<=cnt;i++) swap(e[i],e[i+1]);
}
if (cnt!=n-1) return 0;
return res;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1;i<=m;i++)
{
++cnt;
scanf("%lld%lld%lld",&e[cnt].u,&e[cnt].v,&e[cnt].w);
double out=kruskal();
if (out==0) printf("0\n");
else printf("%0.1lf\n",out/2.0);
}
return 0;
}
D.构造完全图
这里的并查集是个带权并查集(吧 吗?),维护每个集合的个数。升序排列树边,每一条新的边
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n;
struct node{
int u,v,w;
}t[N];
int sum;
int fa[N];
int num[N];
set <int> st;
bool cmp(node a,node b) { return a.w<b.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
int ret=find_fa(fa[x]);
num[x]+=num[fa[x]];
return fa[x]=ret;
}
void _merge(int x,int y)
{
int fa1=find_fa(x),fa2=find_fa(y);
fa[fa1]=fa2;
num[fa2]+=num[fa1];
num[fa1]=0;
}
signed main()
{
scanf("%lld",&n);
for (int i=1;i<n;i++)
{
scanf("%lld%lld%lld",&t[i].u,&t[i].v,&t[i].w);
sum+=t[i].w;
}
for (int i=1;i<=n;i++) fa[i]=i,num[i]=1;
sort(t+1,t+n,cmp);
for (int i=1;i<n;i++)
{
int u=t[i].u,v=t[i].v,w=t[i].w;
if (find_fa(u)==find_fa(v)) continue;
int fa1=find_fa(u),fa2=find_fa(v);
sum+=(num[fa1]*num[fa2]-1)*(w+1);
_merge(u,v);
}
printf("%lld",sum);
return 0;
}
E.最短时间
这道题需要将点权转换为边权。每条边一定会跑两次,跑两次的过程中一定会分别经过一次两个点,所以此时边权
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5;
const int M=1e5+5;
int n,m;
int c[N];
struct node{
int u,v,w;
}e[M];
int fa[N];
int cnt;
int ans=0x3f3f3f3f3f3f3f3f;
bool cmp(node x,node y) { return x.w<y.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+m+1,cmp);
for (int i=1;i<=m*2;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
fa[fa1]=fa2;
ans+=w;
cnt++;
if (cnt>=n-1) return ;
}
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%lld",&c[i]);
ans=min(ans,c[i]);
}
for (int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&e[i].u,&e[i].v,&e[i].w);
e[i].w+=e[i].w+c[e[i].v]+c[e[i].u];
}
kruskal();
printf("%lld",ans);
return 0;
}
F.序列破解
用
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+5;
int n;
struct node{
int u,v,w;
}t[N*N];
int fa[N];
int tol;
int cnt,ans;
bool cmp(node a,node b)
{
return a.w<b.w;
}
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(t+1,t+1+cnt,cmp);
for (int i=1;i<=cnt;i++)
{
int u=t[i].u,v=t[i].v,w=t[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
fa[fa1]=fa2;
ans+=w;
tol++;
if (tol==n) break;
}
}
signed main()
{
scanf("%lld",&n);
for (int i=1;i<=n;i++)
{
for (int j=i;j<=n;j++)
{
++cnt;
scanf("%lld",&t[cnt].w);
t[cnt].u=i-1,t[cnt].v=j;
}
}
kruskal();
printf("%lld",ans);
return 0;
}
G.生物进化
容易发现,这就是道板子。跑一遍
挂分小技巧:忘记双向建边。
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
int n;
struct node{
int u,v,w;
}e[N*N];
int fa[N];
int tol;
int cnt;
vector <int> tr[N];
int ans[N];
bool cmp(node x,node y) { return x.w<y.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
void kruskal()
{
for (int i=1;i<=n;i++) fa[i]=i;
sort(e+1,e+1+tol,cmp);
for (int i=1;i<=tol;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int fa1=find_fa(u),fa2=find_fa(v);
if (fa1==fa2) continue;
fa[fa1]=fa2;
tr[u].push_back(v);
tr[v].push_back(u);
cnt++;
if (cnt==n-1) break;
}
}
void dfs(int u,int _fa)
{
ans[u]=_fa;
int _size=tr[u].size();
for (int i=0;i<_size;i++)
{
int v=tr[u][i];
if (v==_fa) continue;
dfs(v,u);
}
}
signed main()
{
scanf("%lld",&n);
for (int i=1,x;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
scanf("%lld",&x);
e[++tol]={i,j,x};
}
}
kruskal();
dfs(1,0);
for (int i=2;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}
H.保留道路
与公路建设有点像。先按照
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=405;
const int M=5e4+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m;
int g,s;
struct node{
int u,v,g,s;
}e[M];
int top;
int st[N];
int num;
int ans=inf;
int fa[N];
bool cmp(node a,node b){ return a.g<b.g; }
int find_fa(int x)
{
if (fa[x]==0) return x;
return fa[x]=find_fa(fa[x]);
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&m,&g,&s);
for (int i=1;i<=m;i++) scanf("%lld%lld%lld%lld",&e[i].u,&e[i].v,&e[i].g,&e[i].s);
sort(e+1,e+1+m,cmp);
for (int i=1;i<=m;i++)
{
int j;
memset(fa,0,sizeof fa);
for (j=top;j>=1;j--)//插入排序
{
if (e[st[j]].s>e[i].s) st[j+1]=st[j];
else break;
}
top++;
st[j+1]=i;
int num=0;
for (int j=1;j<=top;j++)
{
int fa1=find_fa(e[st[j]].u),fa2=find_fa(e[st[j]].v);
if (fa1!=fa2)
{
fa[fa1]=fa2;
st[++num]=st[j];
}
}
if (num==n-1) ans=min(ans,g*e[i].g+s*e[st[num]].s);
top=num;
}
if (ans==inf) printf("-1");
else printf("%lld",ans);
return 0;
}
I.重建小镇
就是开篇讲的最优比率生成树!
神奇的代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=405;
const int M=1e4+5;
const int MAX=2e15;
int n,m,f;
struct node{
int u,v,c,t;//成本,时间
double w;
}e[M];
int fa[N];
bool cmp(node a,node b) { return a.w<b.w; }
int find_fa(int x)
{
if (fa[x]==x) return x;
return fa[x]=find_fa(fa[x]);
}
bool check(int x)
{
int tmp=1;
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=m;i++) e[i].w=x/(3e6+0.0)*e[i].t+e[i].c;
sort(e+1,e+1+m,cmp);
double k=f+1e-12;
for (int i=2;i<=n;i++)
{
while (tmp<=m&&find_fa(e[tmp].u)==find_fa(e[tmp].v)) tmp++;
fa[find_fa(e[tmp].u)]=find_fa(e[tmp].v);
k-=e[tmp].w;
if (k<0) return false;
}
return true;
}
signed main()
{
scanf("%lld%lld%lld",&n,&m,&f);
for (int i=1;i<=m;i++) scanf("%lld%lld%lld%lld",&e[i].u,&e[i].v,&e[i].c,&e[i].t);
int l=0,r=MAX;
while (l<r)
{
int mid=l+r+1>>1;
if (check(mid)) l=mid;
else r=mid-1;
}
printf("%0.4lf",l/(3e6+0.0));
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!