最短路(ShortextPath)
Dijkstra
#include<bits/stdc++.h>
using namespace std;
/*Dijkstra算法*/
int a[3010][3010],d[3010],n,m;
bool v[3010];
void dijkstra()
{
memset(d,0x3f,sizeof(d));//dist数组
memset(v,0,sizeof(v));//节点标记
d[1]=0;
for (int i=1;i<n;i++)
{//重复进行n-1次
int x=0;
//找到未标记节点中dist最小的
for (int j=1;j<=n;j++)
if (!v[j]&&(x==0||d[j]<d[x]))
x=j;
v[x]=1;
//用全局最小值点X更新其他节点
for (int y=1;y<=n;y++)
d[y]=min(d[y],d[x]+a[x][y]);
}
}
int main()
{
cin>>n>>m;
//构建邻接矩阵
memset(a,0x3f,sizeof(a));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[x][y]=min(a[x][y],z);
}
//求单源最短路径
dijkstra();
for (int i=1;i<=n;i++)
printf("%d\n",d[i]);
}
有向图的Dijkstra算法实现(可输出具体最短路径)
转自http://www.wutianqi.com/?p=1890
/***************************************
* About: 有向图的Dijkstra算法实现
* Author: Tanky Woo
* Blog: www.WuTianQi.com
***************************************/
#include <iostream>
using namespace std;
const int maxnum = 100;
const int maxint = 999999;
// 各数组都从下标1开始
int dist[maxnum]; // 表示当前点到源点的最短路径长度
int prev[maxnum]; // 记录当前点的前一个结点
int c[maxnum][maxnum]; // 记录图的两点间路径长度
int n, line; // 图的结点数和路径数
// n -- n nodes
// v -- the source node
// dist[] -- the distance from the ith node to the source node
// prev[] -- the previous node of the ith node
// c[][] -- every two nodes' distance
void Dijkstra(int n, int v, int *dist, int *prev, int c[maxnum][maxnum])
{
bool s[maxnum]; // 判断是否已存入该点到S集合中
for(int i=1; i<=n; ++i)
{
dist[i] = c[v][i];
s[i] = 0; // 初始都未用过该点
if(dist[i] == maxint)
prev[i] = 0;
else
prev[i] = v;
}
dist[v] = 0;
s[v] = 1;
// 依次将未放入S集合的结点中,取dist[]最小值的结点,放入结合S中。
// 一旦S包含了所有V中顶点,dist就记录了从源点到所有其他顶点之间的
//最短路径长度。
// 注意是从第二个节点开始,第一个为源点。
for(int i=2; i<=n; ++i)
{
int tmp = maxint;
int u = v;
// 找出当前未使用的点j的dist[j]最小值。
for(int j=1; j<=n; ++j)
if((!s[j]) && dist[j]<tmp)
{
u = j; // u保存当前邻接点中距离最小的点的号码。
tmp = dist[j];
}
s[u] = 1; // 表示u点已存入S集合中。
//三角形更新,更新dist。
for(int j=1; j<=n; ++j)
if((!s[j]) && c[u][j]<maxint)
{
int newdist = dist[u] + c[u][j];
if(newdist < dist[j])
{
dist[j] = newdist;
prev[j] = u;
}
}
}
}
// 查找从源点v到终点u的路径,并输出。
void searchPath(int *prev,int v, int u)
{
int que[maxnum];
int tot = 1;
que[tot] = u;
tot++;
int tmp = prev[u];
while(tmp != v)
{
que[tot] = tmp;
tot++;
tmp = prev[tmp];
}
que[tot] = v;
for(int i=tot; i>=1; --i)
if(i != 1)
cout << que[i] << " -> ";
else
cout << que[i] << endl;
}
int main()
{
//freopen("input.txt", "r", stdin);
// 各数组都从下标1开始
// 输入结点数
cin >> n;
// 输入路径数
cin >> line;
int p, q, len; // 输入p, q两点及其路径长度
// 初始化c[][]为maxint
for(int i=1; i<=n; ++i)
for(int j=1; j<=n; ++j)
c[i][j] = maxint;
for(int i=1; i<=line; ++i)
{
cin >> p >> q >> len;
if(len < c[p][q]) // 有重边
{
c[p][q] = len; // p指向q
c[q][p] = len; // q指向p,这样表示无向图
}
}
for(int i=1; i<=n; ++i)
dist[i] = maxint;
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=n; ++j)
printf("%8d", c[i][j]);
printf("\n");
}
Dijkstra(n, 1, dist, prev, c);
// 最短路径长度
cout << "源点到最后一个顶点的最短路径长度: " << dist[n] << endl;
// 路径
cout << "源点到最后一个顶点的路径为: ";
searchPath(prev, 1, n);
}
Dijkstra with priority-queue
#include<bits/stdc++.h>
using namespace std;
/*堆优化*/
const int N=100010,M=1000010;
int head[N],ver[N],edge[N],Next[N],d[N];//顶点 vertex 边 edge 距离 distance
bool v[N];
int n,m,tot;
//大根堆(优先队列),pair的第二维为节点编号
//pair的第一维为dist的相反数(利用相反数变成小根堆)
priority_queue<pair<int,int> >q;
void add(int x,int y,int z)
{
ver[++tot]=y,edge[tot]=z,Next[tot]=head[tot],head[x]=tot;
}
void dijkstra()
{
memset(d,0x3f,sizeof(d));//dist数组
memset(v,0,sizeof(v));//节点标记
d[1]=0;
q.push(make_pair(0,1));
while (q.size())
{//取出堆顶
int x=q.top().second;
q.pop();
if (v[x]) continue;
v[x]=1;
//扫描所有出边
for (int i=head[x];i;i=Next[i])
{
int y=ver[i],z=edge[i];
if (d[y]>d[x]+z)
{//更新,把新的二元组插入堆
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
}
int main()
{
cin>>n>>m;
//构建邻接表
for (int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
//求单源最短路径
dijkstra();
for (int i=1;i<=n;i++)
printf("%d\n",d[i]);
}
SPFA
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
const int M=1000010;
int head[N],ver[M],edge[M],Next[M],d[N];
int n,m,tot;
queue<int>q;
bool v[N];
void add(int x,int y,int z)
{
ver[++tot]=y;
edge[tot]=z;
Next[tot]=head[x];
head[x]=tot;
}
void spfa()
{
memset(d,0x3f,sizeof(d));//dist数组
memset(v,0,sizeof(v));//是否在队列中
d[1]=0,v[1]=1;
q.push(1);
while(q.size())
{//取出队头
int x=q.front();
q.pop();
v[x]=0;
//扫描所有出边
for(int i=head[x];i;i=Next[i])
{
int y=ver[i],z=edge[i];
if(d[y]>d[x]+z)
{//更新,把新的二元组插入堆
d[y]=d[x]+z;
if(!v[y]) q.push(y) , v[y]=1;
}
}
}
}
int main()
{
cin>>n>>m;
//构建邻接表
for (int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
//求单源最短路径
spfa();
for (int i=1;i<=n;i++)
printf("%d\n",d[i]);
}
Bellman-Ford
#include<iostream>
using namespace std;
const int maxnum=100;
const int maxint=99999;
//边
typedef struct edge
{
int u,v;//源点,终点
int weight;//边的权值
}Edge;
Edge edge[maxnum];//保存边的权值
int dist[maxnum];//结点到源点的最小距离
int nodenum,edgenum,source;//结点数,边数,源点
//初始化图
void init()
{
//输入结点数,边数,源点
cin>>nodenum>>edgenum>>source;
for (int i=1;i<=nodenum;i++)
dist[i]=maxint;
dist[source]=0;
for (int i=1;i<=edgenum;i++)
{
cin>>edge[i].u>>edge[i].v>>edge[i].weight;
if (edge[i].u==source)//注意这里的设置初始情况
dist[edge[i].v]=edge[i].weight;
}
}
//松弛计算
void relax (int u,int v,int weight)
{
if (dist[u]+weight<dist[v])
dist[v]=dist[u]+weight;
}
bool Bellman_Ford()
{
for (int i=1;i<nodenum;i++)
for (int j=1;j<=edgenum;j++)
relax (edge[j].u,edge[j].v,edge[j].weight);
bool flag=1;
//判断是否有负环路
for (int i=1;i<=edgenum;i++)
if (dist[edge[i].u]+edge[i].weight<dist[edge[i].v])
{
flag=0;
break;
}
return flag;
}
int main()
{
init();
if (Bellman_Ford())
for (int i=1;i<=nodenum;i++)
cout<<dist[i]<<endl;
return 0;
}
Floyed
/*
*Floyed算法
*/
#include<bits/stdc++.h>
using namespace std;
/*
*目标:把图中 任意 点i与j之间的最短距离都求出来
*原理:根据图的传递闭包思想:if(d[i][k]+d[k][i]<d[i][j]) d[i][j]=d[i][k]+d[k][j]
*时间复杂度:O(n*n*n)
*/
//众所周知:只有五行(甚至只有四行)的核心代码
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (dis[i][k]+dis[k][j]<dis[i][j])
dis[i][j]=dis[i][k]+dis[k][j];
//初始化条件
d[i][j]=0;//自己到自己为0;对角线为0;
d[i][j]=value;//i与j有直接相连的边;
d[i][j]=maxint;//i与j无直接相连的边,一般设maxint为:memset 10即可;
/*
*探究:
*1.path[i][j]记录i到j的最短路径中j的前驱顶点,可用于输出最小路径。
*/
//初始化 i到j有边,则
path[i][j]=i;
path[j][i]=j;
//核心
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (d[i][k]+d[k][j]<d[i][j])
{
d[i][j]=d[i][k]+d[k][j];
path[i][j]=path[k][j];
}
//输出i到j的路线
void dfs (int i,int j)
{
if (i==j) cout<<i<<' ';
else if (path[i][j]>0)
{
dfs(i,path[i][j]);
cout<<j<<' ';
}
}
//2.path[i][j]记录能使i到j的距离变短的节点
//初始化:
path[i][j]=-1;//i与j不通的
path[i][j]=0;//从i到j的最短路径是直接到达的。开始有边的都直接到达。
path[i][j]>0;//从i到j的最短路径要经过点path[i][j];
//核心:
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (d[i][k]+d[k][j]<d[i][j])
{
d[i][j]=d[i][k]+d[k][j];
path[i][j]=k;
}
//输出
cout<<i<<' '<<dfs(i,j)<<' '<<j;
void dfs (int i,int j)
{
if (path[i][j]>0)
{
dfs(i,path[i][j]);
cout<<path[i][j]<<' ';
dfs(path[i][j],j);
}
}
第k短路
这是复制大佬的。。。。。。。。。。
https://blog.csdn.net/chty2018/article/details/53258341
#include<bits/stdc++.h>
#define _ 20100
#define INF 1000100000
using namespace std;
struct node
{
int y,next,v;
}e1[_],e2[_];
struct node2
{
int f,g,id;
friend bool operator < (node2 a,node2 b)
{
return a.f>b.f;
}
};
int n,m,k,len1,len2,Link1[_],Link2[_],dist[_],vis[_],ans[_];
priority_queue<node2>Q;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
return num*f;
}
void insert(int x,int y,int v)
{
e1[++len1].next=Link1[x],Link1[x]=len1,e1[len1].y=y,e1[len1].v=v;
e2[++len2].next=Link2[y],Link2[y]=len2,e2[len2].y=x,e2[len2].v=v;
}
void SPFA()
{
priority_queue<int,vector<int>,greater<int> >q;
memset(dist,0x3f,sizeof(dist));
dist[1]=0,vis[1]=1,q.push(1);
while(!q.empty())
{
int x=q.top();
vis[x]=0;
q.pop();
for(int i=Link2[x];i;i=e2[i].next)
{
int y=e2[i].y;
if(dist[y]>dist[x]+e2[i].v)
{
dist[y]=dist[x]+e2[i].v;
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
}
void Astar()
{
if(dist[n]==INF) return;
node2 temp;
temp.g=0,temp.f=dist[n],temp.id=n;
Q.push(temp);
while(!Q.empty())
{
temp=Q.top();
Q.pop();
if(temp.id==1)
{
ans[++ans[0]]=temp.f;
if(ans[0]>k)
return;
}
for(int i=Link1[temp.id];i;i=e1[i].next)
{
node2 opt;
opt.g=temp.g+e1[i].v;
opt.f=opt.g+dist[e1[i].y];
opt.id=e1[i].y;
Q.push(opt);
}
}
}
int main()
{
n=read(),m=read(),k=read();
for(int i=1;i<=m;i++)
{
int x=read(),y=read(),v=read();
insert(x,y,v);
}
SPFA();
Astar();
for(int i=1;i<=k;i++)
{
if(!ans[i]) printf("-1\n");
else printf("%d\n",ans[i]);
}
return 0;
}
差分约束系统
之前刚学差分约束的时候,不太明白,直到刷了几道题,看了些大佬的博客后,才知道,差分约束系统只是名字高深,其实就是建图的方法而已。。。。。。。。
下面是一道板子题:
p1985 糖果
【描述 Description】
幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,lxhgww需要满足小朋友们的K个要求。幼儿园的糖果总是有限的,lxhgww想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
【输入格式 Input Format 】
输入的第一行是两个整数N,K。
接下来K行,表示这些点需要满足的关系,每行3个数字,X,A,B。
如果X=1, 表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多;
如果X=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果;
如果X=3, 表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果;
如果X=4, 表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果;
如果X=5, 表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果;
【输出格式 Output Format 】
输出一行,表示lxhgww老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出-1。
【样例输入 Sample Input】
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
【样例输出 Sample Output】
11
【时间限制 Time Limitation】
1s
【注释 Hint】
【数据范围】
对于30%的数据,保证 N<=100
对于100%的数据,保证 N<=100000
对于所有的数据,保证 K<=100000,1<=X<=5,1<=A, B<=N
【来源 Source】
scoi2011
#include<bits/stdc++.h>
#define _ 999999
#define inf 0x7fffffff
using namespace std;
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0',ch=getchar();
return num*f;
}
int n,m;
long long ans=0;
int ver[_],edge[_],Next[_],head[_],len;
void add(int x,int y,int z)
{
ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
}
int dist[_],cnt[_];
bool v[_];
queue<int>q;
void SPFA(int s)
{
for (int i=1;i<=n;++i)
dist[i]=-inf;
memset(v,0,sizeof(v));
dist[s]=0,v[s]=1;
q.push(s);
cnt[s]++;
while (!q.empty())
{
int x=q.front();
q.pop();
v[x]=0;
for (int i=head[x];i;i=Next[i])
{
int y=ver[i],z=edge[i];
if (dist[y]<dist[x]+z)
{
dist[y]=dist[x]+z;
if (!v[y])
{
q.push(y),v[y]=1;
if (++cnt[y]>n)
{
puts("-1");
exit(0);
}
}
}
}
}
for (int i=1;i<=n;++i)
ans+=dist[i];
printf("%lld\n",ans);
}
int main()
{
n=read(),m=read();
for (int i=1;i<=m;++i)
{
int z=read(),x=read(),y=read();
//下面就是所谓的差分约束系统了
if (z==1) add(x,y,0),add(y,x,0);
//z=1,表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多
else if (z==2)
{//z=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果
if (x==y) { puts("-1"); exit(0); }
add(x,y,1);
}
else if (z==3) add(y,x,0);
//z=3,表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果
else if (z==4)
{//z=4,表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果
if (x==y) { puts("-1"); exit(0); }
add(y,x,1);
}
else add(x,y,0);
//z=5,表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果
}
for (int i=n;i>=1;--i)
add(0,i,1);//虚设源点
SPFA(0);
return 0;
}
最小生成树(MST)
Prim
#include<bits/stdc++.h>
using namespace std;
int a[3010][3010],d[3010],n,m,ans;
bool v[3010];
void prim()
{
memset(d,0x3f,sizeof(d));
memset(v,0,sizeof(v));
d[1]=0;
for (int i=1;i<n;i++)
{
int x=0;
for (int j=1;j<=n;j++)
if (!v[j]&&(x==0||d[j]<d[x]))
x=j;
v[x]=1;
for (int y=1;y<=n;y++)
if (!v[y])
d[y]=min(d[y],a[x][y]);
}
}
int main()
{
cin>>n>>m;
//构建邻接矩阵
memset(a,0x3f,sizeof(a));
for (int i=1;i<=n;i++)
a[i][i]=0;
for (int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
a[y][x]=a[x][y]=min(a[x][y],z);
}
//求最小生成树
prim();
for (int i=2;i<=n;i++)
ans+=d[i];
cout<<ans<<endl;
return 0;
}
Prim with priority-queue
暂时还没有
Kruskal
#include<bits/stdc++.h>
using namespace std;
struct rec
{
int x,y,z;
}edge[500010];
int fa[100010],n,m,ans,num;
bool operator<(rec a,rec b)
{
return a.z<b.z;
}
int get (int x)
{
if (x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
int main()
{
cin>>n>>m;
for (int i=1;i<=m;i++)
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
//按照边排序
sort(edge+1,edge+m+1);
//并查集初始化
for (int i=1;i<=n;i++)
fa[i]=i;
//求最小生成树
for (int i=1;i<=m;i++)
{
int x=get(edge[i].x);
int y=get(edge[i].y);
if (x==y) continue;
else
{
fa[x]=y;
++num;
ans+=edge[i].z;
}
if (num==n-1) break;
}
cout<<ans<<endl;
return 0;
}
经典例题
p1223 [小数据版]边权差值最小的生成树
【描述 Description】
小 s 最近学了最小生成树,不过聪明的小 s 显然对简单的求最小生成树不感兴趣。现在小 s 想知道,对于一个给定的图,它的所有生成树中,最大边和最小边的边权差最小是多少。
【输入格式 Input Format 】
第一行,两个用空格隔开的整数 N 和 M,分别表示顶点数和边数。
下面 M 行,每行 3 个数 u,v,w,表示 u 和 v 之间有一条权值为 w 的无向边。
【输出格式 Output Format】
一行,一个非负整数,表示最大边和最小边的最小边权差。若图本身不联通,则输出-1
【样例输入 Sample Input】
span1.in
4 5
1 2 3
1 3 5
1 4 6
2 4 6
3 4 7
5 10
1 2 9384
1 3 887
1 4 2778
1 5 6916
2 3 7794
2 4 8336
2 5 5387
3 4 493
3 5 6650
4 5 1422
【样例输出 Sample Output】
span1.out
1
span2.out
1686
【时间限制 Time Limitation】
1s
【注释 Hint】
数据范围
20% N<=10
100% 2<=N<=100,0<=M<=3000
#include<bits/stdc++.h>
using namespace std;
int i;
int n,m,fa[100010],ans,cnt=1;
struct rec
{
int x,y,z;
}edge[100100];
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') { num=(num<<1)+(num<<3)+ch-'0'; ch=getchar(); }
return num*f;
}
bool operator<(rec a,rec b)
{
return a.z<b.z;
}
int get(int x)
{
if (x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
int main()
{
n=read(),m=read();
for (i=1;i<=m;i++)
edge[i].x=read(),edge[i].y=read(),edge[i].z=read();
sort(edge+1,edge+m+1);
ans=edge[m].z;//差值初始化
for(int k=1;k<=m;k++)//以k为最小边进行枚举
{
int sum=0;//最小生成树的边数
for (i=1;i<=n;i++)//生成树初始化
fa[i]=i;
for (i=k;i<=m;i++)//求最小生成树
{
int x=get(edge[i].x);
int y=get(edge[i].y);
if (x==y) continue;
fa[x]=y;
sum++;
if(sum==n-1)//已构成一个最小生成树(从定义出发)
{
ans=min(ans,edge[i].z-edge[k].z);//最大边减去最小边
break;
}
}
if(i==m+1)//
break;
}
if(ans==edge[m].z)//ans未经过改变,说明此图未连通
cout<<-1<<endl;
else
cout<<ans<<endl;
return 0;
}