多校第七场A---Age of Moyu 链式前向星+Dijkstra

                         这里借这个题讲一下什么是链式前向星和他的用处:

一.链式前向星

1.由来:我们都知道图的存储方式有链接矩阵,邻接表两种,邻接矩阵好写但是占内存大,数稍微大点的题目就开不下数组了(适合稠密图);邻接表的指针形式难写易错,vector形式读取太慢大数据下会TLE。那怎么办?这里就产生了一种比较中庸的方式:

链式前向星。

2.概念:听名字,链式前向星就是把数据以链表形式连在一起,那么链接的条件是什么呢?我们回想一下图里面邻接表里存的是每个点的连接点。那么这里的链式前向星存的就是以每个点为起点的所有边。

3.需要:

(1)head[maxn]数组,head[id]表示以 id 点为起点的第一条边,也就是查询以这个点为起点的所有边的开始点,也就是链表头部

(2)

struct Node//Node结构体,存储每条边的终点to , 连在同一起点上的下一条边的编号next , 权值v
{
    int to,next,v;
};

(3)

void AddEdge(int a,int b,int c)//加边函数,连一条以a为起点到达b权值为c的边
{
    node[tot].next = head[a];node[tot].to = b;node[tot].flag = c;head[a] = tot++;
}//这里开始链表骚操作:这段代码是核心,也很好理解就是不断把边的编号连在链表上(tot表示边的编号)

注:(head一般初始化为-1)

很明显,head[i]保存的是以i为起点的所有边中编号最大的那个,而把这个当作顶点i的第一条起始边的位置.

这样在遍历时是倒着遍历的,也就是说与输入顺序是相反的,不过这样不影响结果的正确性.

比如以上图为例,以节点1为起点的边有3条,它们的编号分别是0,3,5   而head[1] = 5

我们在遍历以u节点为起始位置的所有边的时候是这样的:

for(int i=head[u];~i;i=edge[i].next)

那么就是说先遍历编号为5的边,也就是head[1],然后就是edge[5].next,也就是编号3的边,然后继续edge[3].next,也

就是编号0的边,可以看出是逆序的.

下面看一下链式前向星的强大:

二.HDU - 6386  Age of Moyu   

题意:给你n个港口,m个路线。接下来m行给你a,b,c代表从a港口到b港口是航线c。题目规定在一条航线上行驶花费始终为1,但是如果改变一次航线,则花费就增加1。问你从初始1港口到达n港口所需要的最小花费

分析:

(1)思路方面:在做的时候第一想法是用并查集维护一条航线上的点,把同一条航线之间全部两两连线,长度都归为1。然后再跑一遍最短路。但是在两两连线的时候就涉及到了怎么连能减少时间开销,然后gg;

后来发现其实不用并查集,用一个结构体记录一下到该港口的目前花费,然后从该港口p出发去其他港口q时,要判断pq之间航线和到达p的航线是否相同相同的话cost + 0.否则cost + 1;然后更新dis存入队列。(越想越觉得像个优先队列做的BFS)所以还要记录的是到达港口p的航线是什么。这样搜下去就能找到最优答案了。

(2)实现方面:直接vector邻接表超时gg,是vector的查找太慢了,需要用链式前向星。

(3)代码:

#include <iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 200000 + 10;
typedef pair<int,int> P;
struct Node//结构体进行链式前向星存图
{
    int to,next,flag;//终点 + 下个边编号 + 当前航线
};
Node node[2*maxn];
int dis[maxn],head[maxn];
bool judge[maxn];
int n,m,tot;
void AddEdge(int a,int b,int c)//加边函数(分配编号)
{
    node[tot].next = head[a];node[tot].to = b;node[tot].flag = c;head[a] = tot++;
}
struct Arrive//在跑最短路时记录前导航线 + 目前花费 + 当前港口
{
    int flagin,val,portid;
    bool operator <(const Arrive &another)const{//按照花费由小到大排序
          return val>another.val;
    }
    Arrive(int a,int b,int c):flagin(a),val(b),portid(c) {}
};
void Dijkstra(int a)
{
    priority_queue<Arrive>que;
    que.push(Arrive(0,0,a));
    while(!que.empty()){
        Arrive p = que.top();
        que.pop();
        int id = p.portid;
        if(judge[id])continue;
        judge[id] = 1;
        for(int i = head[id];~i;i = node[i].next){//遍历链式前向星
            int cost = p.val + (p.flagin==node[i].flag?0:1);//估测花费(判断航线是否一致)
            if(dis[node[i].to]>cost&&!judge[node[i].to]){//更新
                dis[node[i].to] = cost;
                que.push(Arrive(node[i].flag,cost,node[i].to));
            }
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF){
        tot = 0;//初始化
        memset(dis,INF,sizeof(dis));//这个地方用mem A了而且时间很短,用循环超时QAQ
        //for(int i = 0;i<=n;i++)dis[i] = INF;
        memset(judge,0,sizeof(judge));
        memset(head,-1,sizeof(head));
        for(int i = 0;i<m;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            AddEdge(a,b,c);//无向图双向建边
            AddEdge(b,a,c);
        }
        dis[1] = 0;
        Dijkstra(1);
        printf("%d\n",dis[n]==INF?-1:dis[n]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/81673042