最小生成树(无向图)
1.Prim o(n^2)用于边多
2.kruskal o(mlogm)用于点多
Prim
①每次找到离连通块最近的点t,如果不是第一个点且距离dis为INF,则不连通
②如果不是第一个点,则将距离dis加入答案,然后用这个点t到别的点的距离去更新别的点的dis
注:Prim要执行n次,区别于Dijkstra执行 n-1次
int g[N][N];
int dis[N];
bool vis[N];
void init(){
memset(dis,0x3f,sizeof(dis));
memset(g,0x3f,sizeof(g));
}
int prim(){
dis[1]=0;
int res=0;
for(int i=0;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!vis[j]&&(t==-1||dis[t]>dis[j]))
t=j;
}
vis[t]=true;
if(i&&dis[t]==INF) return INF;//不是第一个点且与当前树不连通
if(i) res+=dis[t];//不是第一个点 把这条边连入生成树中
//更新放在最后,避免负环的影响
for(int j=1;j<=n;j++)
dis[j]=min(dis[j],g[t][j]);//注意是到连通块的最小距离
}
return res;
}
Kruskal
struct node{
int u,v,w;
bool operator<(const node& b)const{
return w<b.w;
}
}edge[N];
int kruskal(){
sort(edge,edge+m);
int res=0,cnt=0;
for(int i=0;i<m;i++)
{
int a=edge[i].a,b=edge[i].b,w=edge[i].w;
a=find(a),b=find(b);
if(a!=b)//a和b不在同一个连通块中,则合并
{
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt<n-1) return INF;
return res;
}
城市通电
题意:若干个村庄,每个村庄可以通过建立发电站或从其他有电的村庄连线,每个村庄建立发电站以及连线的花费不同,求所有村庄都有电的最小花费、建立发电站的村庄、建立的电线
表示有电且可以连向i的村庄中,花费最小的村庄
建立超级源点,建发电站相当于向源点连边
在Prim中,①如果点t是第一次被选,说明没有合适的点可以给该点连边,则在该点建电站;
②如果不是第一次被选,说明该点存在其他已经有电的点可以相连,fa[t]即为可以连的点中代价最小的
用该点去更新其他点,如果更新了点j,则点t即为可以连到点j,且花费最小的点
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=2e3+5;
int n;
PII node[N];
ll w[N];//建电站花费
ll k[N];//建边花费
vector<int> res1;
vector<PII> res2;
int fa[N];//fa[i] 可以连向点i的点中花费最小的
ll dis[N];//i通电的最小花费
bool vis[N];
int cal(int i,int j){
int x=abs(node[i].first-node[j].first);
int y=abs(node[i].second-node[j].second);
return x+y;
}
ll prim(){
ll res=0;
memset(dis,0x3f,sizeof dis);
//相当于建立一个超级源点
//建发电站等于向源点连边
for(int i=1;i<=n;i++){
dis[i]=w[i];
}
for(int i=0;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!vis[j]&&(t==-1||dis[t]>dis[j])){
t=j;
}
}
res+=dis[t];
vis[t]=true;
if(!fa[t]){//如果该点是第一次被选,说明没有合适的点可以给该点连边,则在该点建电站
res1.push_back(t);
}else{//不是第一次被选,说明该点存在其他已经有电的点可以相连,fa[t]即为可以连的点中代价最小的
res2.push_back({fa[t],t});
}
for(int j=1;j<=n;j++){//用该点去更新其他点,如果更新了点j,则点t即为可以连到点j,且花费最小的点
ll d=cal(t,j)*(k[t]+k[j]);
if(dis[j]>d){
dis[j]=d;
fa[j]=t;
}
}
}
return res;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int x,y;cin>>x>>y;
node[i]={x,y};
}
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<=n;i++){
cin>>k[i];
}
cout<<prim()<<endl;
cout<<res1.size()<<endl;
for(auto t:res1){
cout<<t<<" ";
}
cout<<"\n";
cout<<res2.size()<<endl;
for(auto t:res2){
cout<<t.first<<" "<<t.second<<"\n";
}
}
联络员
题意:给出的边中有若干条必选的,求最小生成树大小
思路:对于必选的,先加到生成树中,然后将可选的排序后继续最小生成树
for(int i=1;i<=k;i++)
{
int q,a,b,d;
cin>>q>>a>>b>>d;
//可选的
if(q==2){
ed[++j]={a,b,d};
}//必选的
else{
a=find(a),b=find(b);
p[a]=b;
res+=d;
}
}
sort(ed+1,ed+j+1,cmp);
cout<<kruskal();
新的开始
题意:n个点中,在若干个点建立供电站,连接所有点使得都通电,建立供电站和连接各点都需要一定的花费,求最小花费
思路:建立一个虚拟源点0,和每个点建立一条边,边权为在该点建立供电站的花费,然后求0~n的最小生成树
int prim(){
memset(dis,0x3f,sizeof dis);
dis[0]=0;
int res=0;
for(int i=0;i<=n;i++){
int t=-1;
for(int j=0;j<=n;j++)
if(!vis[j]&&(t==-1||dis[t]>dis[j]))
t=j;
if(i) res+=dis[t];
vis[t]=true;
for(int j=0;j<=n;j++)
dis[j]=min(dis[j],g[t][j]);
}
return res;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int t;cin>>t;
g[0][i]=g[i][0]=t;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>g[i][j];
cout<<prim();
}
北极通讯网络
题意:n个村庄,每个村庄都能联系周围d米以内的村庄,最多给k个村庄建信号站,拥有信号站的村庄可以联系任何村庄,求所有村庄能建立通信网络的情况下,d的最小值
思路:将建立的生成树看作若干个连通块,最初为n个连通块,两两计算村庄之间距离,排序后进行kruskal,当连通块剩下k个时,可以在每个连通块内建立一个信号站,此时枚举到的边(连通块之间的最长边)即为d的最小值
注意:① 当信号站的数量大于等于村庄的数量,就在每个村庄建立信号站,d可以为0
② 当信号站的数量为0,此时d就等于生成树的最大边
double kruskal(){
double maxn=0;
int cnt=n;//n个连通块,到剩余k个连通块时结束
if(n<=k) return 0;
for(int i=1;i<=ed_sum;i++){
int a=ed[i].a,b=ed[i].b;
double d=ed[i].d;
a=find(a),b=find(b);
if(a!=b){
cnt--;
p[a]=b;
maxn=max(maxn,d);
if(cnt==k) return d;
}
}
return maxn;
}
走廊泼水节
题意:在n个点的图中建立最小生成树,再将图加边变为完全图,且保持最小生成树不变,求所加边的长度之和最小
思路:
初始时先将每个点看成一个大小为1的连通块,在每循环到一条可以合并两个连通块的边e时,记e的边长为w,为了形成一个完全图,就要使得两个已经是完全图的连通块中的点有边,但是为了使最后的唯一最小生成树还是原来那棵而且,新增的边一定要大于w,所以最小为w+1
在并查集中维护连通块中点的个数,如果连接点u和点v,则需要额外添加的边的数量为cnt[u]*cnt[v]-1
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 6010;
struct Edge{
int u , v , w;
bool operator < (const Edge &W) const{
return w < W.w;
}
}edge[N];
int n;
int f[N];
int cnt[N];
int find(int x)
{
return f[x] = (f[x] == x ? x : find(f[x]));
}
int main()
{
int T;
cin >> T;
while(T--)
{
cin >> n;
for(int i = 1 ; i <= n ; i++) f[i] = i , cnt[i] = 1;
for(int i = 0 ; i < n - 1; i++)
{
int u , v , w;
cin >> u >> v >> w;
edge[i] = {u , v , w};
}
sort(edge , edge + n - 1);
int res = 0;
for(int i = 0 ; i < n - 1 ; i ++)
{
auto e = edge[i];
int u = find(e.u) , v = find(e.v) , w = e.w;
if(u != v)
{
res += (cnt[u] * cnt[v] - 1) * (w + 1);
f[u] = v;
cnt[v] += cnt[u];
}
}
cout << res << endl;
}
return 0;
}
秘密的牛奶运输(次小生成树)
题意:求次小生成树的大小
思路:
① 求最小生成树,统计标记每条边是树边还是非树边,同时把最小生成树建出来
② 预处理任意两点间路径上的边权最大值和次大值
③ 依次枚举所有非树边尝试替换,求min(sum + w - dist[a][b]),满足w>dist[a][b]
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 510, M = 10010;
int n, m;
struct Edge
{
int a, b, w;
bool f;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}edge[M];
int p[N];
int dist1[N][N], dist2[N][N];
int h[N], e[N * 2], w[N * 2], ne[N * 2], idx;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void dfs(int u, int fa, int maxd1, int maxd2, int d1[], int d2[])
{
d1[u] = maxd1, d2[u] = maxd2;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != fa)
{
int td1 = maxd1, td2 = maxd2;
if (w[i] > td1) td2 = td1, td1 = w[i];
else if (w[i] < td1 && w[i] > td2) td2 = w[i];
dfs(j, u, td1, td2, d1, d2);
}
}
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edge[i] = {a, b, w};
}
sort(edge, edge + m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
LL sum = 0;
for (int i = 0; i < m; i ++ )
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
int pa = find(a), pb = find(b);
if (pa != pb)
{
p[pa] = pb;
sum += w;
add(a, b, w), add(b, a, w);
edge[i].f = true;
}
}
for (int i = 1; i <= n; i ++ ) dfs(i, -1, -1e9, -1e9, dist1[i], dist2[i]);
LL res = 1e18;
for (int i = 0; i < m; i ++ )
if (!edge[i].f)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
LL t;
if (w > dist1[a][b])
t = sum + w - dist1[a][b];
else
t = sum + w - dist2[a][b];
res = min(res, t);
}
printf("%lld\n", res);
return 0;
}