最近在复习最短路算法
然后今天在看差分约束系统的时候看到了这一题 POJ 1511
题目的大意:
给出n个点和n条有向边,求所有点到源点1的来回最短路之和(保证每个点都可以往返源点1)
题目意思很简单,但是数据规模很大,当时知道数据规模很大,用一般的方法肯定会超时或者其他错误,但是还是用基础的方法写了一遍。
还有就是一地方需要注意,就是这题求的是双向单源,所以还需要考虑一下,处理双向的问题很简单,逆向思维,求完第一遍之后,将存储的图方向调反,再SPFA(1)一次,然后两次的结果相加就是所求的答案了,这个逆向的思维很好。赶快找个本子记好。
第一次用的是 SPFA + 邻接矩阵 (然后死的很惨QAQ)
有需要注意的小地方就是这题给的编号是从1开始的,所以在建图的时候遍历就要从1开始,不然肯定是错的,而且错的你还很难找出来,早上花了很久在找这个问题,后来解决了。然后这题如果数据规模不是很大的话可以用SPFA+邻接矩阵来写,但是出题者给了10亿的数据 ,这就比较尴尬了。所以,比较好的方法就是用SPFA+邻接表
下面讲解一下SPFA+邻接表的算法:
#include <cstdio>
#include <cmath>
#include <climits>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define CLR(arr, what) memset(arr, what, sizeof(arr))
const int maxn = 1000010;
const long long MAX = 10000000000000005;
int head[maxn];
int key[maxn];
int next[maxn];
int node[maxn];
int num;
int nodenum,edgenum;
bool vis[maxn];
long long dis[maxn];
struct Edge
{
int u;
int v;
int weight;
}edge[maxn];
void add(int u,int v,int cost){
key[num] = cost;
next[num] = head[u];
node[num] = v;
head[num] = num;
num++;
}
void init(){
i = 0;
CLR(head,-1);
for(int i=1;i<=nodenum;i++){
dis[i] = MAX;
}
}
long long SPFA(int start){
long long total = 0;
int temp;
queue<int> q;
while(!q.empty())
q.pop();
CLR(vis,false);
dis[start] = 0;
vis[start] = true;
q.push(start);
while(!q.empty()){
temp = q.front();
q.pop();
vis[temp] = false;
for(int i=head[temp];i!=-1;i=next[i]){
if(dis[node[i]]>dis[temp]+key[i]){
dis[node[i]] = dis[temp] + key[i];
if(!vis[node[i]]){
q.push(node[i]);
vis[node[i]] = true;
}
}
}
}
for(int i=1;i<=nodenum;i++)
total += dis[i];
return total;
}
int main(int argc, char const *argv[])
{
int T;
int top;
long long answer;
scanf("%d",&T);
while(T--){
top = answer = 0;
scanf("%d%d",&nodenum,&edgenum);
init();
for(int i=0;i<edgenum;i++){
scanf("%d%d%d",&edge[top].u,&edge[top].v,&edge[top].weight);
add(edge[top].u,edge[top].v,edge[top].weight);
top++;
}
answer+=SPFA(1);
init();
for(int i=0;i<top;i++)//反置图
add(edge[i].v,edge[i].u,edge[i].weight);
answer+=SPFA(1);
printf("%lld\n",answer);
}
return 0;
}
代码如上,其实对于邻接表不止一个方法建
还有用到指针的,这里是用head数组来更新下一个结点的序号来实现的
然后就是建立邻接表的时候大致有一个模子:
第一步:
typedef struct
{
int to;
int w;
int next;
}Edge;
Edge e[MAX];
int pre[MAX];
//初始化
memset(pre,-1,sizeof(pre));
//输入
scanf("%d %d %d",&from,&to,&w1);
e[i].to = to;
e[i].w = w1;
e[i].next = pre[from];
pre[from] = i;
i++;
第二步:
/now为弧尾结点序号,i为now所发出的边序号,adj为弧头结点序号,w为now–>adj这条边的权值/
for(i = pre[now]; i != -1; i = edge[i].next)
{
int adj = edge[i].to;
int w = edge[i].w;
//do something…
//做你想做的事情。
}
因为邻接表不是向邻接矩阵那样简单易懂所以可能理解起来会有点麻烦
多练应该会好一点吧 ,遇到最短路的时候先尝试用邻接表建立一下看看然后再慢慢找漏洞,相信会有一天可以弄精邻接表的。
好了 今天就到这里了 去吃饭喽 吃完饭继续刷题哦 :)