• 7.11最小生成树+最短路


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

    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

  • 相关阅读:
    System.DateUtils 1. DateOf、TimeOf 简单修饰功能
    Servlet高级
    Servlet基础
    AOP的基本概念
    Bean的定义及作用域的注解实现
    Bean
    centos7系统下,配置学校客户端网络记录
    CUDA并行编程思维过程
    CUDA(GPU)OPENCV
    3-决策树
  • 原文地址:https://www.cnblogs.com/mudrobot/p/13330160.html
Copyright © 2020-2023  润新知