题目链接:Invitation Cards
分析:这是我写的第一道SPFA,没想到第一题,尽然以6000多ms的速度爬过,汗!而且加了邻接表优化,这一道题,教会我两个知识点,狂赞:好题!这题大意是:CCS有自愿者每天去各个站点宣传,让别人去看戏剧,要求这P个自愿者一个来回所需的最小花费。先求一次最短路,算出CCS到每个站点的最短距离,加起来,再将所有的边都反向一次,再计算一次CCS到每个站点的最短距离,这就相当于原图中每个站点到CCS的距离,再加起来,就是一个来回,到达每个站点的最短距离之和了。这题一点是:数据构成稀疏矩阵,用邻接表存储,比较省空间;第二点:量比较大,需用较快的算法实现,我用了SPFA,这样我就用了两次,跑了6000多ms,YM !这题还有一小陷阱:中间或结果可能出现超出32bits的整数。
代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define NN 1000004
#define INF 0x7fffffff
struct node{
int sta; //边的始点
int end; //边的终点
int key; //边的权值
}edge[NN];
struct node1{
int key; //邻接的点
int next; //下一个点的下标
}pre[NN];
int n, p, q;
int que[NN], root[NN];
__int64 dis[NN];
char mark[NN];
void Add(int a, int b, int c, int id){
int tmp;
edge[id].sta = a;
edge[id].end = b;
edge[id].key = c;
pre[id].key = b;
pre[id].next = -1;
/*由于点很多,边相对较少,所以用邻接表来存*/
if (root[a] == -1){
root[a] = id;
}else{
tmp = root[a];
while (pre[tmp].next != -1){
tmp = pre[tmp].next;
}
pre[tmp].next = id;
}
}
void Spfa(){
int i, num, tmp, cur;
mark[1] = 1;
que[0] = 1;
num = 1;
// dis赋初值
for (i = 1; i <= p; i++){
dis[i] = INF;
}
tmp = root[1];
dis[1] = 0;
// 手动模拟队列
for (i = 0; i < num; i++){
cur = que[i];
tmp = root[cur];
while (tmp != -1){
if (dis[cur] + edge[tmp].key < dis[pre[tmp].key]){
if (!mark[pre[tmp].key]){mark[pre[tmp].key] = 1;
que[num++] = pre[tmp].key;
}
dis[pre[tmp].key] = dis[cur] + edge[tmp].key;
}
tmp = pre[tmp].next;
}
mark[cur] = 0;
}
}
int main()
{
int i, a, b, c;
__int64 sum;
scanf("%d", &n);
while (n--){
scanf("%d%d", &p, &q);
memset(root, -1, sizeof(root));
for (i = 1; i <= q; i++){
scanf("%d%d%d", &a, &b, &c);
Add(a, b, c, i);
}
memset(mark, 0, sizeof(mark));
Spfa();
sum = 0;
for (i = 2; i <= p; i++){
sum += dis[i];
}
/*将所有的边反向一次,再求得的起点到其余各点的值即为原图中各点回到起点的值*/
memset(root, -1, sizeof(root));
for (i = 1; i <= q; i++){
a = edge[i].end;
b = edge[i].sta;
c = edge[i].key;
Add(a, b, c, i);
}
memset(mark, 0, sizeof(mark));
Spfa();
for (i = 2; i <= p; i++){
sum += dis[i];
}
printf("%I64d\n", sum);
}
return 0;
}
邻接表:邻接表是,将所有节点n的后继结点用一个链表的形式存起来,以节点n做首节点。
SPFA:以队列的形式优化Bellman_Ford算法,时间复杂度优化到O(ke),其中k为所有顶点进队的平均次数,可以证明k一般小于等于2,e为边数。