1.单源最短路的建图方式
(1)热浪(djsl优化)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int N=2510;
const int M=6210*2;
int n,m,s,t;
int dist[N];//起点s到各点的距离
bool st[N];
int h[N],e[M],ne[M],w[M],idx;
//邻接表
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void djsl()
{
memset(dist,0x3f,sizeof(dist));
priority_queue<pii,vector<pii>,greater<>>heap;//小根堆
dist[s]=0;
heap.push({dist[s],s});
//h的第二个存起点到某点,第一个存起点到这个点的最短距离
while(heap.size())
{
pii t=heap.top();
heap.pop();
int u=t.second;
if(st[u]!=0)continue;
st[u]=1;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[u]+w[i])
{
dist[j]=dist[u]+w[i];
heap.push({dist[j],j});
}
}
}
}
int main()
{
memset(h,-1,sizeof(h));
cin>>n>>m>>s>>t;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
djsl();
cout<<dist[t]<<endl;
return 0;
}
(2)信使(Floy三层循环)
#include<bits/stdc++.h>
using namespace std;
//无向连通图
//求起点1到所有点的距离总和最短
const int N=100+10;
const int M=200+10;
#define inf 0x3f3f3f3f
int n,m;
//稠密图邻接矩阵
//Floyd
int g[N][N];
void Floy()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}
}
}
}
//因为N最大为100,可以使用Floy算法三层循环
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)g[i][j]=0;
else g[i][j]=inf;
}
}
for(int i=0;i<m;i++)
{
int a,b,k;
cin>>a>>b>>k;
g[a][b]=g[b][a]=min(g[a][b],k);
}
Floy();
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,g[1][i]);
}
if(ans==0x3f3f3f3f)cout<<-1<<endl;
else cout<<ans<<endl;
return 0;
}
(3)香甜的黄油(djsl优化)
#include<bits/stdc++.h>
using namespace std;
//djsl:枚举每个黄油的位置(枚举起点的位置)
//使其到其它每个点的距离之和最短
//注意点:一个牧场可能没有牛,但是在确保能和其它点连通的情况下可以是起点
//堆优化djsl(邻接表)
const int N=2e4+10;
#define pii pair<int,int>
int h[N],e[N],ne[N],w[N],idx;
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
int n,p,c;
//n头牛,p个牧场,c路的个数
int dist[N];
//每个节点到起点的最短路
bool st[N];
int cnt[N];
//记录每个牧场有多少奶牛
int ans=0x3f3f3f3f;//答案取min操作注意初始无穷大
void djsl(int x)
{
memset(st,0,sizeof(st));
memset(dist,0x3f3f3f3f,sizeof(dist));
dist[x]=0;
priority_queue<pii,vector<pii>,greater<>>heap;
heap.push({dist[x],x});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int u=t.second;
if(st[u])continue;
st[u]=1;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[u]+w[i])
{
dist[j]=dist[u]+w[i];
heap.push({dist[j],j});
}
}
}
return;
}
int main()
{
memset(h,-1,sizeof(h));//邻接表记得初始
cin>>n>>p>>c;
while(n--)
{
//cow为牛所在牧场的编号
int cow;
cin>>cow;
cnt[cow]++;
}
while(c--)
{
int u,v,k;
cin>>u>>v>>k;
add(u,v,k);
add(v,u,k);
}
//枚举每个牧场作为起点
for(int i=1;i<=p;i++)
{
djsl(i);
int sum=0;
//判断这个起点是否与其它点连通
bool flag=true;
for(int j=1;j<=p;j++)
{
if(cnt[j])//有奶牛
{
if(dist[j]==0x3f3f3f3f)
{
flag=false;
break;
}
//可以走到这个点
sum+=cnt[j]*dist[j];
}
}
if(flag)ans=min(ans,sum);
}
cout<<ans<<endl;
return 0;
}
(4)最小花费(朴素djsl)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pdi pair<double,int>
const int N=2000+10;
const int M=1e5+10;
double g[N][N];
int n,m,s,h;
double dist[N];//从起点到某点此时的剩余的钱为多少,取max
bool st[N];
void djsl()
{
dist[s]=100;//假设初始100
//迭代n次
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
{
if((!st[j])&&(t==-1||dist[t]<dist[j]))t=j;
}
st[t]=1;
for(int j=1;j<=n;j++)
{
dist[j]=max(dist[j],dist[t]*g[t][j]);
}
}
}
int main()
{
cin>>n>>m;
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
double cc=(100.0-z)/100;//cc为可以得到的百分比
g[x][y]=g[y][x]=max(g[x][y],cc);
}
cin>>s>>h;
djsl();
printf("%.8lf\n",100.0/dist[h]*100);
return 0;
}
//扣除率=最后得到的钱/初始有的钱=dist[g]/100(先假设初始为100,先算出来,之后再用来求最终初始)
//初始有的钱=最后得到的钱/扣除率(根据题意,最后得到的钱为100)
//ans=100/(dist[g]/100)=100/dist[g]*100
(5)最优乘车(Floy三层循环)
输入一组一组数字(但是不先告诉个数)
#include<bits/stdc++.h>
using namespace std;
const int N=500+10;
const int M=100+10;
//数据量小可三层循环
int m,n;
//m条巴士线,n个站点
//直接枚举中间点Floy
int d[N][N];
//d[i][j]表示i到j的最小换乘次数
int main()
{
cin>>m>>n;
//输入每条巴士线经过的站点
//输出最少换乘次数(无法达到输出-1)
memset(d,0x3f,sizeof(d));
getchar();
while(m--)
{
string str;
getline(cin,str);
stringstream scin(str);
int cnt=0,t,s[510];
while(scin>>t)s[cnt++]=t;
//假设同一条线上换乘次数都为1,最后答案减去1(乘第一条线不用换乘)
for(int i=0;i<cnt;i++)
{
for(int j=i+1;j<cnt;j++)
{
d[s[i]][s[j]]=1;
}
}
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
if(d[1][n]==0x3f3f3f3f)cout<<-1<<endl;
else cout<<d[1][n]-1<<endl;
return 0;
}
(6)昂贵的聘礼(朴素djsl)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=110;
//思路:终点为1号点,起点为虚拟源点S为0号点(表示直接买所需要的钱数)
//对于等级差距,可以枚举等级区间每次求一次S~1的最短路,取最小值即可
//邻接矩阵存图
int w[N][N],level[N];
//w[i][j]=cost表示从i花cost的钱到j
int m,n;
bool st[N];
int dist[N];
int djsl(int l,int r)
{
memset(st,0,sizeof(st));
memset(dist,0x3f,sizeof(dist));
dist[0]=0;
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=0;j<=n;j++)//注意找最小花费时要包括虚拟源点0直接买的情况
{
if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;
}
st[t]=1;
for(int j=1;j<=n;j++)
{
if(level[j]>=l&&level[j]<=r)
{
dist[j]=min(dist[j],dist[t]+w[t][j]);
}
}
}
return dist[1];
}
int main()
{
memset(w,0x3f,sizeof(w));
cin>>m>>n;
//m为等级差距
for(int i=1;i<=n;i++)w[i][i]=0;
for(int i=1;i<=n;i++)
{
int price,cnt;
cin>>price>>level[i]>>cnt;
w[0][i]=min(w[0][i],price);
for(int j=1;j<=cnt;j++)
{
int num,cost;
cin>>num>>cost;
w[num][i]=min(w[num][i],cost);
}
}
int res=0x3f3f3f3f;
//枚举等级区间
//商家起始点在1
for(int i=level[1]-m;i<=level[1]+m;i++)
{
res=min(res,djsl(i,i+m));
}
cout<<res<<endl;
return 0;
}
2.单源最短路的综合应用
(1)新年好(djsl+dfs)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int N=5e4+10;
const int M=2e5+10;
//1->2->3->4->5->6(数字表编号)
//即求(1->2)min+(2->3)min+(3->4)min+(4->5)min+(5->6)min
//1.先预处理出从1,a,b,c,d,e出发到其它所有点的单源最短路径
//2.DFS所有拜访顺序,对于每一种拜访顺序,可以通过查表的方式算出最短距离
int n,m;
int x1,x2,x3,x4,x5;
int h[N],e[M],ne[M],w[M],idx;
int dist[6][N];
//dist[i][j]表示以第i个亲戚为起点去往j处地(source[k]=j亲戚编号是k)的最短距离
bool st[N];
int source[6];
//source[i]中i表示亲戚编号,值表示亲戚家的居住地
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void djsl(int start,int dist[])
{
memset(st,0,sizeof(st));
memset(dist,0x3f3f3f3f,N);
dist[start]=0;
priority_queue<pii,vector<pii>,greater<>>heap;//小根堆
heap.push({dist[start],start});
while(heap.size())
{
auto t=heap.top();
heap.pop();
int u=t.second;
if(st[u])continue;
st[u]=1;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[u]+w[i])
{
dist[j]=dist[u]+w[i];
heap.push({dist[j],j});
}
}
}
}
// 枚举每种拜访次序,求出最小距离
// 拜访了u个人,自己是第1个人;当前起点是source[start],当前走过的距离是distance
// 当u为6时即拜访完
int dfs(int u,int start,int distance)
{
if(u==6)return distance;
int res=0x3f3f3f3f;
for(int i=1;i<=5;i++)
{
if(!st[i])
{
int next=source[i];
st[i]=1;//亲戚i已走
res=min(res,dfs(u+1,i,dist[start][next]+distance));
st[i]=0;
}
}
return res;
}
int main()
{
memset(h,-1,sizeof(h));
cin>>n>>m;
source[0]=1;
for(int i=1;i<=5;i++)cin>>source[i];
while(m--)
{
int x,y,t;
cin>>x>>y>>t;
add(x,y,t);
add(y,x,t);
}
for(int i=0;i<=5;i++)djsl(source[i],dist[i]);
//dfs(u,start,distance)
//表示拜访了u个人,自己是第一个人,当前起点为source[start](source[0]),当前走过的距离是distance
memset(st,0,sizeof(st));
cout<<dfs(1,0,0)<<endl;
return 0;
}
(2)通信线路(spfa)
#include<bits/stdc++.h>
using namespace std;
const int N=1e4*3+100;
const int M=1100;
int n,m,k;
int e[N],h[N],ne[N],ver[N],idx;
int dist[M][M];
bool vis[M];
queue<int>q;
void add(int a,int b,int c)
{
e[idx]=b;
ver[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void spfa(int s)
{
memset(dist,0x3f,sizeof(dist));
dist[s][0]=0;
vis[s]=1;
q.push(s);
while(q.size())
{
int t=q.front();
q.pop();
vis[t]=0;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i],z=ver[i],w=max(z,dist[t][0]);
if(dist[j][0]>w)
{
dist[j][0]=w;
if(!vis[j])
{
q.push(j);
vis[j]=1;
}
}
for(int p=1;p<=k;p++)
{
int w=min(dist[t][p-1],max(dist[t][p],z));
if(dist[j][p]>w)
{
dist[j][p]=w;
if(!vis[j])
{
q.push(j),vis[j]=1;
}
}
}
}
}
}
int main()
{
memset(h,-1,sizeof(h));
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
spfa(1);
int ans=1e9;
for(int i=0;i<=k;i++)
{
ans=min(ans,dist[n][i]);
}
if(ans==1e9)puts("-1");
else cout<<ans<<endl;
return 0;
}
(3)道路与航线(拓扑+djsl)
//道路:双向,边权非负(求最短路用djsl)
//航线:单向,边权可正可负,且无环
//(拓扑序:有向无环图,至少存在一个入度为0的点)
//单源最短路和拓扑序的结合
//如果边权非负,用djsl,O(mlogn)
//如果是拓扑图,不管边权正还是负,均可按拓扑序扫描,时间复杂度是线性的
//1.对于道路:输入所有双向道路,dfs出所有连通块,计算两个数组:id[]存储每个点属于哪个连通块;
//vector<int>block[]存下每一个连通块里的点
//2.对于航线:输入所有单向航线,同时统计出每个连通块的入度
//3.按照拓扑序依次处理每个连通块,先将所有入度为0的连通块的编号加入队列中,
//每次从队头取出一个连通块的编号bid,将block[bid]中的所有点加入堆中,
//然后对堆中所有点跑djsl算法。
//4.每次取出堆中最小的点ver,然后遍历ver的所有邻点j,如果id[ver]==id[j](在同一个连通块里),
//如果j能被更新,则将j插入堆中;如果id[ver]!=id[j](j在另外一个连通块中相当于访问到一条有向边),
//则将id[j]这个连通块的入度减1,如果减成0了,则将其插入拓扑排序的队列中。
//时间复杂度:O(m1logn+m2logn+m3logn+...)=O((m1+m2+m3+m4+...)logn)=O(mlogn)(m为边数,n为点数)
//单源最短路体现:dist[]初始化为正无穷,起点初始为dist[s]=0,从起点开始搜。
//道路:双向,边权非负(求最短路用djsl)
//航线:单向,边权可正可负,且无环
//(拓扑序:有向无环图,至少存在一个入度为0的点)
//单源最短路和拓扑序的结合
//如果边权非负,用djsl,O(mlogn)
//如果是拓扑图,不管边权正还是负,均可按拓扑序扫描,时间复杂度是线性的
//1.对于道路:输入所有双向道路,dfs出所有连通块,计算两个数组:id[]存储每个点属于哪个连通块;
//vector<int>block[]存下每一个连通块里的点
//2.对于航线:输入所有单向航线,同时统计出每个连通块的入度
//3.按照拓扑序依次处理每个连通块,先将所有入度为0的连通块的编号加入队列中,
//每次从队头取出一个连通块的编号bid,将block[bid]中的所有点加入堆中,
//然后对堆中所有点跑djsl算法。
//4.每次取出堆中最小的点ver,然后遍历ver的所有邻点j,如果id[ver]==id[j](在同一个连通块里),
//如果j能被更新,则将j插入堆中;如果id[ver]!=id[j](j在另外一个连通块中相当于访问到一条有向边),
//则将id[j]这个连通块的入度减1,如果减成0了,则将其插入拓扑排序的队列中。
//时间复杂度:O(m1logn+m2logn+m3logn+...)=O((m1+m2+m3+m4+...)logn)=O(mlogn)(m为边数,n为点数)
//单源最短路体现:dist[]初始化为正无穷,起点初始为dist[s]=0,从起点开始搜
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define x first
#define y second
#define inf 0x3f3f3f3f
const int N=25010,M=150010;
int n,mr,mp,s;
int h[N],e[M],ne[M],w[M],idx;
int id[N];
vector<int>block[N];
int bcnt;//连通块个数
int dist[N];
bool st[N];
int din[N];//存下每个连通块的入度
queue<int>q;
void djsl(int bid);
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void dfs(int u,int bid)
{
id[u]=bid;
block[bid].push_back(u);
//遍历当前u的所有邻边
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!id[j])
{
dfs(j,bid);
}
}
}
void topsort()
{
memset(dist,0x3f,sizeof(dist));
dist[s]=0;
//遍历所有连通块
for(int i=1;i<=bcnt;i++)
{
if(!din[i])
{
q.push(i);
}
}
while(q.size())
{
int t=q.front();
q.pop();
djsl(t);
}
}
void djsl(int bid)
{
priority_queue<pii,vector<pii>,greater<pii>>heap;
//遍历每个堆中的所有点
for(auto ver:block[bid])
{
heap.push({dist[ver],ver});
}
while(heap.size())
{
auto t=heap.top();
heap.pop();
int ver=t.y,distance=t.x;
if(st[ver])continue;
st[ver]=1;
for(int i=h[ver];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>distance+w[i])
{
dist[j]=distance+w[i];
if(id[j]==id[ver])
{
heap.push({dist[j],j});
}
}
if(id[j]!=id[ver]&&(--din[id[j]]==0))//入度减一同时做判断,即使判断无法进入也会减一
{
//id[j]的连通块入度为0
q.push(id[j]);
}
}
}
}
int main()
{
cin>>n>>mr>>mp>>s;
memset(h,-1,sizeof(h));
while(mr--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
//暴搜所有连通块
for(int i=1;i<=n;i++)
{
if(!id[i])
{
dfs(i,++bcnt);
}
}
while(mp--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
din[id[b]]++;
//b所在的连通块入度加一
}
topsort();
for(int i=1;i<=n;i++)
{
if(dist[i]>inf/2)//不一定是正无穷,因为有负权边,大于一个大数即可
{
puts("NO PATH");
}
else cout<<dist[i]<<endl;
}
return 0;
}
(4)最优贸易(spfa正向求最小和反向求最大)
#include<bits/stdc++.h>
using namespace std;
//所有从1到N先买再卖的最大收益
//按照分界点分类(分界点是在某点买入或者卖出)
//设k为分界点
//从1到k买入的最小值dmin(k)
//从k到n卖出的最大值dmax(k)
//差价:dmax(k)-dmin(k)
//s1->k<-s2 dmin(k)=min{dmin(s1),dmin(s2),...,wk}
//即求最短路(用spfa求dmin的值)(dmax用反向图从n号点出发求最大值)
//Bellmax-ford(dp):for迭代n-1次,在for每条边,用三角不等式更新
const int N=1e5+10;
const int M=2e6+10;
int n,m;
int w[N];//每个水晶球价格
int hs[N],ht[N],e[M],ne[M],idx;
//hs正向图,ht反向图
int dmin[N],dmax[N];
bool st[N];
int q[N];//队列spfa
void add(int h[],int a,int b)
{
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void spfa(int h[],int dist[],int type)
{
int hh=0,tt=1;
if(type==0)//求最小值
{
memset(dist,0x3f,sizeof(dmin));
dist[1]=w[1];
q[0]=1;
}
else//最大值
{
memset(dist,-0x3f,sizeof(dmax));
dist[n]=w[n];
q[0]=n;
}
while(hh!=tt)
{
int t=q[hh++];//取出队头
if(hh==N)hh=0;
st[t]=0;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if((type==0&&dist[j]>min(dist[t],w[j]))||(type==1&&dist[j]<max(dist[t],w[j])))
{
if(type==0)dist[j]=min(dist[t],w[j]);
else dist[j]=max(dist[t],w[j]);
if(!st[j])
{
q[tt++]=j;
if(tt==N)tt=0;
st[j]=1;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
memset(hs,-1,sizeof(hs));
memset(ht,-1,sizeof(ht));
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(hs,a,b),add(ht,b,a);//单向道路的正向图和反向图
if(c==2)add(hs,b,a),add(ht,a,b);//双向道路的另外一条边的正向图和反向图
}
spfa(hs,dmin,0);
spfa(ht,dmax,1);
//枚举所有点为分界点求最大值
int res=0;
for(int i=1;i<=n;i++)
{
res=max(res,dmax[i]-dmin[i]);
}
cout<<res<<endl;
return 0;
}
3.单源最短路的扩展应用
(1)选择最佳线路(spfa多个起点到一个终点-反向-多个终点到一个起点)
#include<bits/stdc++.h>
using namespace std;
const int N=1000+10;
const int M=200000+10;
//起点不确定,但是终点确定求最短路
//可以建反向图,把终点当做起点,再把多个起点当成多个终点枚举求最短路
int n,m,s;
//n:点数 m:边数 s:终点
int h[N],e[M],ne[M],w[M],idx;
int start[N];
int scnt;
bool st[N];
int d[N];
//d[x]表示s到x的最短距离
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
void spfa()
{
memset(d,0x3f3f3f3f,sizeof(d));
memset(st,0,sizeof(st));
d[s]=0;
st[s]=1;
queue<int>q;
q.push(s);
while(q.size())
{
auto t=q.front();
q.pop();
st[t]=0;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(d[j]>d[t]+w[i])
{
d[j]=d[t]+w[i];
if(!st[j])
{
st[j]=1;
q.push(j);
}
}
}
}
}
int main()
{
while(cin>>n>>m>>s)
{
memset(h,-1,sizeof(h));
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(b,a,c);
}
spfa();
//只做一遍spfa求起点s到各个点的距离
cin>>scnt;
int res=0x3f3f3f3f;
for(int i=0;i<scnt;i++)
{
int x;
cin>>x;
res=min(res,d[x]);
}
if(res==0x3f3f3f3f)cout<<-1<<endl;
else cout<<res<<endl;
}
return 0;
}
4.Floyd算法
(1)牛的旅行
#include<bits/stdc++.h>
using namespace std;
//用floyd算法求出任意两点之间的最短距离
//求maxd[i],表示和i连通的且距离i最远的点的距离
//情况1:所有maxd[i]的最大值(已连通)
//情况2:枚举在哪两个点之间连边。i,j需要满足d[i,j]=inf
// maxd[i]+dist[i,j]+maxd[j]
#define pii pair<int,int>
const int N=150;
const double inf=1e20;
int n;
pii q[N];
char g[N][N];
double d[N][N];
double maxd[N];
double get_dist(pii a,pii b)
{
double dx=a.first-b.first;
double dy=a.second-b.second;
return sqrt(dx*dx+dy*dy);
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
{
cin>>q[i].first>>q[i].second;
}
for(int i=0;i<n;i++)cin>>g[i];
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(i!=j)
{
if(g[i][j]=='1')d[i][j]=get_dist(q[i],q[j]);
else d[i][j]=inf;
}
}
}
//floyd(求任意两个点的最短距离)
for(int k=0;k<n;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(d[i][j]<inf)
{
//求maxd[i],表示和i连通的且距离i最远的点的距离
maxd[i]=max(maxd[i],d[i][j]);
}
}
}
double res1=0;
for(int i=0;i<n;i++)res1=max(res1,maxd[i]);
double res2=inf;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(d[i][j]>=inf)
{
res2=min(res2,get_dist(q[i],q[j])+maxd[i]+maxd[j]);
}
}
}
printf("%.6lf\n",max(res1,res2));
return 0;
}
(2)排序
#include<bits/stdc++.h>
using namespace std;
//传递闭包(floyd)
//d[i,j]=1,d[i,j]=0
//1.矛盾,d[i,j]=1
//2.唯一确定(i!=j),d[i,j]和d[j,i]一个为1(不断求出未被标记过的最小值)
//3.顺序不唯一
//迭代次数t:m组关系中到第t组关系能够出现三种情况中的一种
//(1)判断一个人的名次是否确定:比自己成绩好的人数+比自己成绩差的人数=除自己外的总人数
//(2)根据m组关系,如何判断一个人是否比另一个人的成绩好->
//用floyd:判断两点之间是否可以到达(dist[x][y]!=1e9->x的成绩比y好)
//对于一个点,遍历其余所有点就可以得到:比该点成绩好的点数和比该点成绩差的点数
//(3)判断矛盾:从 x 能到达 y 并且从 y 也能到达 x(即,x 比 y 成绩好并且 y 比 x 成绩好),那就是出现矛盾了。
//(4)输出排好的序列:将所有点按照能到达的点数从小到大排序,然后依次输出编号。
#define pii pair<int,int>
const int N=210;
int T,n,m,k;
int a[N],dist[N][N];
pii ans[N];
void floyd(int x)//以x为中转点,更新其它点
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dist[i][j]=min(dist[i][j],dist[i][x]+dist[x][j]);
}
}
}
bool pd()//判断所有点是否确定
{
for(int i=1;i<=n;i++)
{
int cnt=0;
for(int j=1;j<=n;j++)
{
if(dist[i][j]!=1e9)cnt++;
}
ans[i]={cnt,i};//i能到达的所有点数(i比这些人成绩好)
for(int j=1;j<=n;j++)
{
if(i!=j&&dist[j][i]!=1e9)cnt++;//能到达当前点的数
}
if(cnt!=n)return 0;//名次无法确定
}
sort(ans+1,ans+n+1);
return 1;//名次唯一确定
}
int main()
{
while(cin>>n>>m&&n)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i!=j)dist[i][j]=1e9;
}
}
bool flag=0;
for(int i=1;i<=m;i++)
{
char a,t,b;
cin>>a>>t>>b;
int x=a-'A'+1;
int y=b-'A'+1;
//y>x即加一条y到x的边
//如果已有x到y,就矛盾了
if(!flag&&dist[x][y]!=1e9)
{
printf("Inconsistency found after %d relations.\n", i);
flag=1;
}
dist[y][x]=1;
floyd(x),floyd(y);//分别以x,y为中转点更新其它点
if(!flag&&pd())//所有点已确定
{
printf("Sorted sequence determined after %d relations: ", i);
for(int i=1;i<=n;i++)
{
cout<<char(ans[i].second+'A'-1);
}
cout<<".\n";
flag=1;
}
}
if(!flag) printf("Sorted sequence cannot be determined.\n"); //无法确定
}
return 0;
}
(3)观光之旅
#include<bits/stdc++.h>
using namespace std;
//无向图的最小环问题(Floyd)
//<->i<->k<->j<->最短路径长为:d[i,j]+w[i,k]+w[k,j]
//d[i,j]=d[i,k]+d[k,j]其中k为i到j中编号最大的一个点
//floyd:for(k:for(i:for(j)))当在k时,即是d[i,j]i->j只经过(1~(k-1))这么多个点的最短路径
//最小环:至少包含3个点,环上的节点不重复,环上的边的长度之和最小
const int N=100+10;
const int M=1e4+10;
int n,m;
int d[N][N],g[N][N];
int p[N][N];
int path[N],cnt;
void getpath(int i,int j)
{
//i和j本身为一条边
if(p[i][j]==0)return;
int k=p[i][j];
getpath(i,k);
path[cnt++]=k;
getpath(k,j);
}
int main()
{
cin>>n>>m;
memset(g,0x3f,sizeof(g));
for(int i=1;i<=n;i++)g[i][i]=0;
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
g[a][b]=g[b][a]=min(g[a][b],c);
}
memcpy(d,g,sizeof(d));
int res=0x3f3f3f3f;
for(int k=1;k<=n;k++)
{
//求最小环,枚举i和j,且k是i,j上最大的编号
for(int i=1;i<k;i++)
{
for(int j=i+1;j<k;j++)
{
//因为g初始很大可能爆int,需要转成longlong
if((long long)d[i][j]+g[j][k]+g[k][i]<res)
{
res=d[i][j]+g[j][k]+g[k][i];
cnt=0;
path[cnt++]=k;
path[cnt++]=i;
getpath(i,j);
path[cnt++]=j;
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(d[i][j]>d[i][k]+d[k][j])
{
d[i][j]=d[i][k]+d[k][j];
p[i][j]=k;//转移中间点
}
}
}
}
if(res==0x3f3f3f3f)puts("No solution.\n");
else
{
for(int i=0;i<cnt;i++)
{
cout<<path[i]<<" ";
}
cout<<endl;
}
return 0;
}
5.最小生成树
最小生成树构成后所有的点都被连通一共有n-1条边。(n个点)
而最短路只要从起始点到达目的地走的是最短的路径即可,与所有的点连不连通没有关系。
Kruskal算法可以很好地利用并查集来实现其核心思想,即判断边的两个节点是否在同一个集合中,从而避免形成回路。
在并查集中,每个元素都属于一个集合,并且每个集合有一个代表元素。初始时,每个节点都是一个独立的集合,即每个节点的代表元素是其自身。
Kruskal算法利用并查集的步骤如下:
初始化并查集,将图中的每个节点都作为一个独立的集合,即每个节点的代表元素是其自身。
将图中的所有边按照权重从小到大进行排序。
依次考虑排序后的每条边。
对于当前考虑的边(u, v),查找u和v的代表元素,如果它们的代表元素不同,说明u和v不在同一个集合中,将这条边(u, v)加入最小生成树,并将u和v所在的集合合并(即将其中一个代表元素指向另一个代表元素)。
重复步骤3和步骤4,直到最小生成树中包含了所有的节点(边的数量等于节点数减1)。
通过并查集的合并操作,Kruskal算法能够高效地判断两个节点是否在同一个集合中,从而避免形成回路,并构建出最小生成树。当所有的边都考虑完毕时,最小生成树就构建完成了。
(1)最短网络(kruscal)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
//连接起所有点的最小总长度(最小生成树)
struct node
{
int u,v,w;
bool operator<(const node &W)const
{
return w<W.w;
}
}s[N];
int p[N];//并查集的parent数组
int n,m;
int ans;
int find(int x)
{
if(p[x]==x)return p[x];
else return p[x]=find(p[x]);
}
void merge(int x,int y)
{
int px=find(x);
int py=find(y);
p[px]=py;
}
void kruscal()
{
for(int i=1;i<=n;i++)p[i]=i;
sort(s+1,s+1+m);
int cnt=n-1;//边数
for(int i=1;i<=m;i++)
{
int u=s[i].u;
int v=s[i].v;
int w=s[i].w;
if(find(u)==find(v))continue;
//不在同一个集合
merge(u,v);
cnt--;
ans+=w;
if(cnt==0)break;
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
int aa;
cin>>aa;
m++;//边数++
s[m]={i,j,aa};
}
}
kruscal();
cout<<ans<<endl;
return 0;
}
(2)局部网
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
//求出最小生成树的边权和ans,再用边权总和sum减去ans即为答案
int n,k;
struct node
{
int u,v,w;
bool operator<(const node &W)const
{
return w<W.w;
}
}s[N];
int p[N];
int ans;
int sum;
int find(int x)
{
if(p[x]==x)return p[x];
else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
int pu=find(u);
int pv=find(v);
p[pu]=pv;
return;
}
void kruscal()
{
for(int i=0;i<=n;i++)p[i]=i;
sort(s+1,s+k+1);
int cnt=n-1;//全部点连通
for(int i=1;i<=k;i++)
{
int u=s[i].u;
int v=s[i].v;
int w=s[i].w;
if(find(u)==find(v))continue;
merge(u,v);
cnt--;
ans+=w;
if(cnt==0)break;
}
}
int main()
{
cin>>n>>k;
for(int i=1;i<=k;i++)
{
int u,v,w;
cin>>u>>v>>w;
s[i]={u,v,w};
sum+=w;
}
kruscal();
cout<<sum-ans<<endl;
return 0;
}
(3)繁忙的都市
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int M=8000+10;
int n,m;
struct node
{
int u,v,w;
bool operator<(const node &W)const
{
return w<W.w;
}
}s[N];
int p[N];
int res;
int find(int x)
{
if(p[x]==x)return p[x];
else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
int pu=find(u);
int pv=find(v);
p[pu]=pv;
}
void kruscal()
{
for(int i=0;i<=n;i++)p[i]=i;
sort(s+1,s+1+m);
int cnt=n-1;
for(int i=1;i<=m;i++)
{
int u=s[i].u;
int v=s[i].v;
int w=s[i].w;
if(find(u)==find(v))continue;
merge(u,v);
cnt--;
res=w;//操作到后面排好序的最大一条边即为最大边权值(且由于最小生成树已保证最小)
if(cnt==0)break;
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
s[i]={u,v,w};
}
kruscal();
cout<<n-1<<" "<<res<<endl;
return 0;
}
(4)联络员
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct node
{
int u,v,w;
bool operator<(const node &W)const
{
return w<W.w;
}
}s[N];
int n,m;
int res;
int p[N];
int find(int x)
{
if(p[x]==x)return p[x];
else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
int pu=find(u);
int pv=find(v);
p[pu]=pv;
}
void kruscal()
{
sort(s+1,s+1+m);
for(int i=1;i<=m;i++)
{
int u=s[i].u;
int v=s[i].v;
int w=s[i].w;
if(find(u)==find(v))continue;
merge(u,v);
res+=w;
}
}
int main()
{
cin>>n>>m;
for(int i=0;i<=n;i++)p[i]=i;//初始要放主函数里
for(int i=1;i<=m;i++)
{
int pp,u,v,w;
cin>>pp>>u>>v>>w;
s[i]={u,v,w};
//若必选直接连边相加
if(pp==1)
{
merge(u,v);
res+=w;
}
}
kruscal();
cout<<res<<endl;
return 0;
}
(5)连接格点
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m;//n行m列
//二维坐标转一维编号[x,y]
//num=(x-1)*n+y;
int p[N];
int num;//总编号
int res;
int find(int x)
{
if(p[x]==x)return p[x];
else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
int pu=find(u);
int pv=find(v);
p[pu]=pv;
}
void kruscal()
{
//纵向合并花费一金币,先合并
for(int j=1;j<=m;j++)
{
for(int i=1;i<n;i++)
{
int u=(i-1)*n+j;
int v=i*n+j;//i不能为n,当i==n-1时这里已经代表第n行
if(find(u)==find(v))continue;
merge(u,v);
res++;
}
}
//横向合并花费2金币,后合并
for(int i=1;i<=n;i++)
{
for(int j=1;j<m;j++)
{
int u=(i-1)*n+(j+1);//j不能为m,当j==m-1时这里已经代表第m列
int v=(i-1)*n+j;
if(find(u)==find(v))continue;
merge(u,v);
res+=2;
}
}
}
int main()
{
cin>>n>>m;
num=n*m;
for(int i=0;i<=num;i++)p[i]=i;
int x1,y1,x2,y2;
while(cin>>x1>>y1>>x2>>y2)
{
int idx1=(x1-1)*n+y1;
int idx2=(x2-1)*n+y2;
merge(idx1,idx2);
}
kruscal();
cout<<res<<endl;
return 0;
}