标签:lse code 这一 ant 工作 void 进入 比较 循环
其实这一部分的知识在前面我也算是学了一些了,今天老师讲的是一些应用部分,结果我发现自己一脸懵逼,有些题我甚至连这是最短路都没有看出来。看来还并没有修炼到一定的水准,并且我还是发现一些自己还没有学习过的知识,下面我们就先从链式前向星开始说起。
首先我们就来介绍一下这个东西是个什么。
图的存储一般有两种:邻接矩阵,前向星。
若图是稀疏图,边很少,开二维数组是非常浪费的。
若点非常的多,(如10000个点)开二维数组就会爆空间,所以说只能用前向星来做。
前向星的效率不是很高,优化后变成链式前向星,效率有所提升。
这里有两个东西
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
标签:lse code 这一 ant 工作 void 进入 比较 循环
原文地址:https://www.cnblogs.com/mudrobot/p/13330160.html