码迷,mamicode.com
首页 > 其他好文 > 详细

7.11最小生成树+最短路

时间:2020-07-17 16:11:40      阅读:51      评论:0      收藏:0      [点我收藏+]

标签:lse   code   这一   ant   工作   void   进入   比较   循环   

技术图片

其实这一部分的知识在前面我也算是学了一些了,今天老师讲的是一些应用部分,结果我发现自己一脸懵逼,有些题我甚至连这是最短路都没有看出来。看来还并没有修炼到一定的水准,并且我还是发现一些自己还没有学习过的知识,下面我们就先从链式前向星开始说起。

1、链式前向星

首先我们就来介绍一下这个东西是个什么。

图的存储一般有两种:邻接矩阵,前向星。

若图是稀疏图,边很少,开二维数组是非常浪费的。

若点非常的多,(如10000个点)开二维数组就会爆空间,所以说只能用前向星来做。

前向星的效率不是很高,优化后变成链式前向星,效率有所提升。

(1)结构

这里有两个东西

1、结构体数组edge存边,edge[i]表示第i条边。

2、head[i]存以i为起点的第一条边(在egde中的下标)

注意:每次新加的边作为第一条边!!!

struct EDGE{
	int next;   //下一条边的存储下标(默认0) 
	int to;     //这条边的终点 
	int w;      //权值 
}; 
EDGE edge[500010];

2.增边:若以点i为起点的边新增了一条,在edge中的下标为j.

那么edge[j].next=head[i];然后head[i]=j.

即每次新加的边作为第一条边,最后倒序遍历


void Add(int u, int v, int w) {  //起点u, 终点v, 权值w 
	//cnt为边的计数,从1开始计 
	edge[++cnt].next = head[u];
	edge[cnt].w = w;
	edge[cnt].to = v;
	head[u] = cnt;    //第一条边为当前边 
} 

3、遍历

遍历以st为起点的边

for(int i=head[st]; i!=0; i=edge[i].next)

i开始为第一条边,每次指向下一条(以0为结束标志) (若下标从0开始,next应初始化-1)important!!

下面是自己用链式前向星写的Spfa:

代码如下:

#include<bits/stdc++.h>
using namespace std;
int dis[10005];
struct sd{
	int to,val,nxt;
}edge[500005];
int head[10005];
bool vis[10005];
int cnt=0;
int n,m,s;
void add(int from,int to,int val)
{
	edge[cnt].nxt=head[from];edge[cnt].to=to;edge[cnt].val=val;head[from]=cnt;cnt++;
}
void spfa()
{
	for(int i=1;i<=n;++i)
	{
		dis[i]=2147483647;
	}
	queue<int> q;
	q.push(s);
	dis[s]=0;
	vis[s]=true;
	while(!q.empty())
	{
		int now=q.front();q.pop();
		vis[now]=false;
		for(int i=head[now];i!=-1;i=edge[i].nxt)
		{
			if(dis[edge[i].to]>dis[now]+edge[i].val)
			{
				dis[edge[i].to]=dis[now]+edge[i].val;
				if(!vis[edge[i].to])
				{
					vis[edge[i].to]=true;
					q.push(edge[i].to);
				}
			}
		}
	}
}
int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;++i)
	{
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	spfa();
	for(int i=1;i<=n;++i)
	{
		printf("%d ",dis[i]);
	}
	return 0;
} 

Dijsktra priority版本的链式前向星版本后面再说。

下面进入今天的正题,我们以一道例题来感受一下图论中建图的奥妙,虽然这道题本人自己想了很久也并没有做出来,但是,我已经深刻的理解到了题解中的做法,所以说呢,这里所用的代码并不是本人自己写的,大家理解一下,毕竟是“NOI”难度的,所用我觉得还是有一些困难:

下面我们就上题吧!

题目传送门: P3645 [APIO2015]雅加达的摩天楼

这道题其实看上去并没有想象中的那么难,但是如果你那样想的话,很可能是因为你并没有注意到数据范围上的限制,这道题对数据范围的限制可以说还是比较厉害的,所以想直接暴力建图的同学最好还是在仔细考虑一下到底行不行,反正我试过是不行的,那么我们如何缩小我们建图所耗费的时间复杂度呢?其实,我们这样想就好了,我们把所有能力小于sqrt(n)的doge所有边的可能情况都先把图建出来,然后如果出现了能力大于sqrt(n)的doge我们就进行暴力建边,注意:对于那些能力是0的doge我们要从高层的图中往最底层建一条权值为0的又向边,然后对于那些我们加进来有p能力但是能力没有超过sqrt(n)的doge我们要从最底层建一条到第p层权值为0的有向边,这样他的能力才能得到发挥(因为我们小于sqrt(n)的能力的边是已经经过我们预处理过的,我们不是像大于sqrt(n)的能力的doge一样是直接在他所在的位置进行暴力建边)。最后这些工作都做完了以后,咱们最难的一部分建图就已经搞定了,接下来只需要跑一个spfa或Dijsktra就可以了。相信大家都应该听懂了吧!代码中也有注释,不懂的还可以再看一看:

代码如下:

#include<bits/stdc++.h>
#define RG register
#define il inline 
#define N 5500000
#define Inf 2
#define U unsigned short
#define pos(i,j) (i*n+j)
using namespace std;
struct ed{int nxt,to,c;}e[30005*500];
int head[30005*105],dis[30005*105],n,m,q,b,s,t;
bool in[30005*105];
int tot;
void add(int u,int v,int c){e[tot].nxt=head[u];e[tot].to=v;e[tot].c=c;head[u]=tot;tot++;}
void ADD(int u,int v,int c){add(u,v,c),add(v,u,c);}
void spfa(){//链式前向星spfa 
  queue<int>que;memset(dis,Inf,sizeof(dis));int SS=dis[t];
  que.push(s),in[s]=true,dis[s]=0;
  while(!que.empty()){
    int u=que.front();que.pop();in[u]=false;
    for(int i=head[u];i!=-1;i=e[i].nxt)if(dis[e[i].to]>dis[u]+e[i].c){
    int v=e[i].to;dis[v]=dis[u]+e[i].c;
    if(!in[v])que.push(v),in[v]=true;
      }
  }if(dis[t]==SS)cout<<"-1";
  else cout<<dis[t];
}
int main(){
  memset(head,-1,sizeof(head));
  cin>>n>>m;U int len=min((int)sqrt(n),100);//下面四个for循环是来加短边的 ,其中每一行的第一个for循环是用来建第i层图的,第i层图doge的跳跃能力为i 
  for(RG int i=1;i<=len;++i)for(int j=1;j<=n;++j)add(pos(i,j),j,0);//单独判断跳跃能力是0doge 
  for(RG int i=1;i<=len;++i)for(int j=1;j<=n-i;++j)ADD(pos(i,j),pos(i,j+i),1);//添加从j到所有j可以到的最短的边的路径 
  for(RG int i=1;i<=m;++i){int b,p;
    cin>>b>>p;b++;//caution 
    if(i==1)s=b;if(i==2)t=b;//0号doge和1号doge分别的位置代表的是起点和终点 
    if(p<=len)add(b,pos(p,b),0);//?
    //if(p>len) 
    else {
      for(int j=1;j*p+b<=n;++j)add(b,b+j*p,j);//暴力加比较长的边这里是正向加 
      for(int j=1;b-j*p>0;++j)add(b,b-j*p,j);//暴力加比较短的边这里是往回加 
    }
  }spfa();
  return 0;
}

By njc

7.11最小生成树+最短路

标签:lse   code   这一   ant   工作   void   进入   比较   循环   

原文地址:https://www.cnblogs.com/mudrobot/p/13330160.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!