WEEK_8(最短路径&图)

这周讲了关于求最短路径的算法--Dijkstra算法,至于Floyd是另外学的,最后看来发现还是Floyd更好理解一点,接下来就简单介绍一下关于这两种算法

后来写题的时候发现原来上课和题目根本是两回事,为了写题,俺还去学了图论,说实话,学吐了

B3647 Floyd

Floyd 算法是解决图论问题的比较经典的算法,主要用于解决带权有向图节点对之间的最短路径问题,也可以处理带权无向图的最短路径。

该算法主要用到了动态规划的思想,每次迭代是遍历是否会经过第 i 点与第 j 点之间所有可能的点,并判断经过这个点与直接从 i 到 j 两种情况下走过的路径长短,每次取较短路径,直到求得所有节点对之间的最短路径。

那么状态转移方程就不难求得:dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j])

dis表示第 i 点到第 j 点的较短距离,k表示中转的点,代码如下:

#include
using namespace std;
int n,m;
const int N=105;
#define inf 0x7ffffff
int maap[N][N];
int dis[N][N];
int u,v,w;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		dis[i][j]=inf;
		dis[i][i]=0;
	}
	for(int i=1;i<=m;i++)
	{
		cin>>u>>v>>w;
		dis[u][v]=min(dis[u][v],w);
		dis[v][u]=dis[u][v];//邻接矩阵 
	}
	for(int i=1;i<=n;i++)//经过i是否会使路径变短 
	for(int j=1;j<=n;j++)//起始点 
	for(int k=1;k<=n;k++)//终点 
	dis[j][k]=min(dis[j][k],dis[j][i]+dis[i][k]);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		cout<

 代码提到的邻接矩阵,可以看成是一种图的数学表示方式

本题邻接矩阵:

WEEK_8(最短路径&图)_第1张图片

 具体步骤:

1、创建一个二维数组,用于存储任意两个节点之间的最短路径长度。初始时,该数组的值为图中两个节点之间的边的权重。如果两个节点之间没有边相连,则用一个较大的值(如inf)表示。

2、通过遍历所有可能的中间节点,对于每对节点(i,j),检查经过这个中间节点k的路径是否更短。如果更短,则更新最短路径数组的值为经过中间节点的路径长度和。

3、重复步骤2,直到遍历完所有可能的中间节点。

4、最终得到的二维数组即为图中任意两个节点之间的最短路径长度。


P4779 单源最短路径 Dijkstra

dijkstra算法思路是:用一个集合记录已求得的最短路径的顶点,初始时把源点放入集合,而集合每并入一个新顶点 ,都要修改源点到当前集合中顶点到源点的最短路径长度值

好家伙,我学的迪杰斯特拉算法是:建一个邻接矩阵,然后从起点出发,找到最短,次短,第三短路径。。。

结果这一题,n太大了,结果全爆QAQ

下面是死亡代码:

#include
using namespace std;
int n,m;
int s;
const int N=1e4+1;
vector > mapp;
#define inf 0x7fffffff
int dis[N];
int vis[N];
int nextt;
int u,v,w;
int dismin[N];
int main()
{
	cin>>n>>m>>s;
	vis[s]=1;
	for(int i=1;i<=n;i++)
	dis[i]=inf;
	dis[s]=0;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		mapp[i][j]=inf;
		mapp[i][i]=0;
	}
	for(int i=1;i<=m;i++)
	{
		cin>>u>>v>>w;
		mapp[u][v]=min(mapp[u][v],w);
	}
	for(int i=1;i<=n;i++)
	{
		while(s!=i)
		{
			dismin[i]=inf;
			for(int j=1;j<=n;j++)
			{
				if(mapp[s][j]!=inf)
				dis[j]=min(dis[j],mapp[s][j]+dis[s]);
				if(!vis[j]&&dis[j]

 0分走人~~

后来呢,去看佬们写的代码,看到了两种存图的思路,除了邻接矩阵外,还有邻接表链式前向星邻接表

邻接表是图的一种最主要存储结构,用来描述图上的每一个点。对于图的每个顶点建立一个容器( n个顶点建立 n 个容器),第 i 个容器中的结点包含顶点 vi 的所有邻接顶点。

#include
using namespace std;
#define ll long long
const int N=1e5+5;
ll n,m,s,d[N];//d[i]为从起点到 i的距离 
struct edge
{
	ll v,w;
};
vector g[N];
bool vis[N];
struct heap
{
	ll v,dis;
	bool operator<(const heap &p)const
	{
		return dis>p.dis;
	}//优先队列重载 "<",队头元素最小 
};
priority_queue q;
void dij_heap()
{
	d[s]=0;
	q.push(heap{s,0});
	while(q.size())
	{
		heap t;
		t=q.top();
		q.pop();
		ll k=t.v;
		if(vis[k])
		continue;
		vis[k]=true;
		for(int j=0;jwk)
			{
				d[v]=wk;
				q.push(heap{v,d[v]});
			}
		}
	}
}
int main()
{
	cin>>n>>m>>s;
	for(int i=0;i<=n;i++)
	{
		vis[i]=false;
		d[i]=INT_MAX/2;
	}
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		g[u].push_back(edge{v,w});
	}
	dij_heap();
	ll ans=(111<<31)-1;
	for(int i=1;i<=n;i++)
	if(d[i]>INT_MAX/2)
	cout<

链式前向星

前向星

是一个存图的工具,一种特殊的边集数组。所以前向星数组对应的其实是边的信息,下标就是边的下标。把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大,并且记录下以某个点为起点的所有边在数组中的起始位置和存储长度

链式前向星

就是静态建立的邻接表,时间效率为O(m),空间效率也为O(m)。遍历效率也为O(m).m是边数

---来自万能的知乎

 链式前向星存的是以[1,n]为起点的边的集合

对于链式前向星,需要知道以下两个变量的作用:

  • next,表示与这个边起点相同(同根)的上一条边(父节点)的编号
  • head[ i ],表示以 i 为起点的最后一条边的编号(添加 i为新边)
struct Edge
{
    int to,w,next;//终点,边权,同起点的上一条边的编号
}edge[maxn];//边集
int head[maxn];//head[i],表示以i为起点的最后一条边在边集数组的位置(编号)
void add(int u,int v,int w)//加边,u为起点,v为终点,w为边权
{
    edge[cnt].w=w;//存权值
    edge[cnt].to=v;//存终点
    edge[cnt].next=head[u];//u的上一条边的编号,即与这个边起点相同的上一条边的编号
    head[u]=cnt;//更新以u为起点上一条边的编号
    cnt++;
}

P2661 [NOIP2015 提高组] 信息传递

这题用到了并查集

定义:
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。如:可以用并查集判断一个森林中有几棵树、某个节点是否属于某棵树等。

主要构成:
并查集主要由数组pre[ ]和函数find( )join( )构成
数组 pre[ ] 记录每个点的前驱节点

find(x) ——查找指定节点 x 属于哪个集合

join(x,y) ——合并节点 x 和 y 

作用:求连通分支数(一个图中各个点之间的可达关系)

---摘自csdn某神犇

 find()

find(x)用于找到 x 的祖先节点

int find(int x)
{
	while(pre[x]!= x)
	x=pre[x];
	return x;
}

join()

join(x,y)用于将两棵树联合起来(x指向y,或者y指向x)

void join(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
    pre[fx]=fy;
}

 这题其实没有多复杂,两种思路:

第一种:连接一个点到另一个点前,先用并查集判断是否构成一个环,如果构成了环,就直接记录答案,然后保留最小的

第二种:(还是不理解)假如说信息由A传递给B,那么就连一条由A指向B的边,同时更新A的父节点,A到它的父节点的路径长也就是B到它的父节点的路径长+1。

这样就建好了一个图,之后信息传递的所有环节都按照这些路径。游戏结束的轮数,也就是这个图里最小环的长度。

如果有两个点祖先节点相同,那么就可以构成一个环,长度为两个点到祖先节点长度之和+1。

---摘自洛谷题解anyway大神

第一种:

#include
using namespace std;
const int N=2e5+5;
int fa[N];
int t;
int n;
int ans=0x7fffffff;
int find(int x,int &cnt)
{
	cnt++;
	if(fa[x]==x)//有环 
	return x; 
	else
	return find(fa[x],cnt);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	fa[i]=i;
	for(int i=1;i<=n;i++)
	{
		int cnt=0,f;
		cin>>t;
		if(find(t,cnt)==i)//找t的父节点,并判断是否是i 
		ans=min(cnt,ans);//找到环并保留 
		else
		fa[i]=t;
	}
	cout<

第二种:

#include
using namespace std;
const int N=2e5+5;
int f[N],d[N];//f表示父节点,d表示到其祖先结点的路径长 
int fan,minn,last;
int t;
int n;
int fa(int x)
{
	if(f[x]!=x)
	{
		int last=f[x];
		f[x]=fa(f[x]);
		d[x]+=d[last];
	}
	return f[x];
}
void check(int a,int b)
{
	int x=fa(a),y=fa(b);
	if(x!=y)
	{
		f[x]=y;
		d[a]=d[b]+1;
	}
	else
	minn=min(minn,d[a]+d[b]+1);
	return ;                   
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	f[i]=i;
	minn=0x7fffffff;
	for(int i=1;i<=n;i++)
	{
		cin>>t;
		check(i,t);
	}
	cout<

P1144 最短路计数

Dijkstra两种存图方式,都给写了一遍

和Dijkstra模版那题差不多,只是多了一个计数,其他的照搬照抄

链式前向星:

#include
using namespace std;
const int N=1e6+5;
const int M=4e6+5;
int n,m;
int x,y;
int dis[N];//起点到某点的最短路大小
int head[N];
int js[N];//起点到某点的最短路条数
int md=1e5+3;
bool vis[N];
struct Edge
{
	int w,v;
	int next;
}edge[M];
int cnt=1;
void add(int u,int v,int w)
{
	edge[cnt].v=v;//终点 
	edge[cnt].w=w;//权值 
	edge[cnt].next=head[u];
	head[u]=cnt;
	cnt++;
}
struct heap
{
	int v,dis;
	bool operator<(const heap &p)const
	{
		return p.dis q;
void dij(void)
{
	memset(vis,false,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[1]=0;
	js[1]=1;
	vis[1]=true;
	q.push((heap){1,0});
	while(q.size())
	{
		heap k;
		k=q.top();
		q.pop();
		if(k.dis!=dis[k.v])
		continue;
		for(int i=head[k.v];i;i=edge[i].next)
		{
			if(k.dis+edge[i].w==dis[edge[i].v])
			js[edge[i].v]=(js[k.v]+js[edge[i].v])%md;
			if(dis[edge[i].v]>dis[k.v]+edge[i].w)
			{
				dis[edge[i].v]=dis[k.v]+edge[i].w;
				js[edge[i].v]=js[k.v];
				q.push((heap){edge[i].v,dis[edge[i].v]});
			}
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		add(x,y,1);
		add(y,x,1);
		cnt++;
	}
	dij();
	for(int i=1;i<=n;i++)
	cout<

邻接表:

#include
using namespace std;
const int N=1e5+5;
const int M=4e5+5;
int inf=0x7fffffff;
int md=1e5+3;
struct Edge
{
	int v,w;
};
int n,m;
int x,y;
int dis[N];
int js[M];
bool vis[N];
vector g[N];
struct heap
{
	int v,dis;
	bool operator<(const heap &p)const 
	{
		return p.dis q;
	heap t;
	dis[1]=0;
	js[1]=1;
	q.push((heap){1,0});
	while(q.size())
	{
		t=q.top();
		int vv=t.v,dd=t.dis;
		q.pop();
		if(dd!=dis[vv])
		continue;
		for(int i=0;i>n>>m;
	memset(vis,false,sizeof(vis));
	for(int i=0;i<=n;i++)
	dis[i]=inf;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		g[x].push_back((Edge){y,1});
		g[y].push_back((Edge){x,1});
	}
	dij();
	for(int i=1;i<=n;i++)
	cout<

P8794 [蓝桥杯 2022 国 A] 环境治理

很诡异一题

交了无数次,最后发现保存方式有问题,但是这个问题很诡异。。。

思路:首先肯定不能暴力了,然后我们就要开始考虑简化,通过二分并判断能否在这么多天数内实现路径最少,然后主函数就是二分思路了,check函数里面,因为是每个点到每个点的距离,因此要用Floyd算法,最后累加就可得到结果啦

代码如下:

#include
using namespace std;
#define ll long long
ll n;
ll q;
ll cnt=-1;
const int N=105;
ll lim[N][N];
ll k;
ll dis[N][N];
ll dust[N][N];//每条边权值 
bool check(ll x)//经过了x天 
{
	
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	dust[i][j]=dis[i][j];
	for(int i=1;i<=n;i++)
	{
		ll jj=x/n;//至少经过jj次循环 
		if(x%n>=i)//第i天是第jj+1次循环后的x%n天内 
		jj++;
		for(int j=1;j<=n;j++)
		{
			dust[i][j]-=jj;
            if(dust[i][j]>n>>q;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		cin>>dust[i][j];
		dis[i][j]=dust[i][j];
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	cin>>lim[i][j];
	ll left=0,right=100000*n;
	while(left<=right)
	{
		ll mid=(left+right)/2;
		if(check(mid))
		{
			cnt=mid;
			right=mid-1;
		}
		else
		left=mid+1;
	}
	cout<

以下为选做题:

P1522 [USACO2.4] 牛的旅行 Cow Tours

P1462 通往奥格瑞玛的道路

P1119 灾后重建

P1266 速度限制

你可能感兴趣的:(算法,数据结构,图论)