调试了一下午终于调出来了,原来WA了九个点,竟然是因为“优雅”的建图打错了一个字母。。
最短路径
【题面】
题目描述
输入格式
输出格式
输出答案,为一个整数。
样例输入
样例输出
2
数据范围
【思路】
GQH大神太强了,现场想出标算。假如我们把那个有k个点的点集称为集合S,如果我们把这个集合分成两个互不相交的子集(并且使它们的并集为原集合) S1,S2 S 1 , S 2 ,然后分别对 S1 S 1 和 S2 S 2 进行缩点。
(缩点:用一个新点去代替原来集合中的所有的点,假如原来集合 S1 S 1 中有一个点 u u ,那么的所有出边都要从这个“新点”连出, u u 的所有入边都要连入这个“新点”。当然了,要重建一个图,并且这张新图中将不存在一个叫的点。)
很显然(数学老师好像很不喜欢学生写显然),如果最终的“答案路径”的起点和终点被划分到了不同的集合,那么缩点之后的两个集合间的最短路就是最终的答案。(因为是有向图,我们可以分别从两个“新点”跑一次SPFA,求出 S1→S2,S2→S1 S 1 → S 2 , S 2 → S 1 两个最短路。)
正确性:因为缩点之后原来所有的边仍然存在,因为不存在负边,最短路要么是两个新点之间的边,要么是某个新点内部的边,要么是经由新点之外的一条路径从一个新点到另一个新点。而缩点就相当于忽略了两个新点内部的边,但并没有改变其他的路径。【解释了上文:如果最终的“答案路径”的起点和终点被划分到了不同的集合,那么缩点之后的两个集合间的最短路就是最终的答案。】
那么我们怎样缩点呢?因为这条路径的起点和终点一定是两个不同的点(保证不存在自环,出题的大神好像在考试的时候说了)。两个点的编号不一样,说明它们至少有一位二进制位是不同的,那么我们可以枚举每一个二进制位,每次把集合S中,编号在这个二进制位上为0的点,记为 S1 S 1 ;编号在这个二进制位上为1的点,记为 S2 S 2 ,然后进行缩点、跑SPFA。我们只需要 O(lgN) O ( lg N ) 次划分就能保证得到正确答案。
【代码】
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
#include <set>
#include <algorithm>
using namespace std;
#define maxn (100000 + 10)
#define inf (0x7f7f7f7f)
int s[maxn], k, n, m;
struct edge{
int f, t, c;
} edges[maxn];
struct Graph{
int from[maxn], to[maxn], cost[maxn], nxt[maxn], ecnt;
int fst[maxn], dis[maxn], vis[maxn];
void addedge(int f, int t, int c){
int ne = ++ecnt;
from[ne] = f; to[ne] = t; cost[ne] = c;
nxt[ne] = fst[f]; fst[f] = ne;
}
int SPFA(int S, int T){
//printf("\nSPFA s= %d t=%d\n",s,t);
memset(dis, 0x7f, sizeof(dis));
memset(vis, 0x00, sizeof(vis));
queue<int> q;
q.push(S); vis[S] = 1; dis[S] = 0;
while(!q.empty()){
int x = q.front(); q.pop(); vis[x] = 0;
//printf("x = %d\n", x);
for(int i=fst[x]; i!=0; i=nxt[i]){
int t = to[i];
//printf("try t = %d\n",t);
if(dis[t] > dis[x] + cost[i]){
dis[t] = dis[x] + cost[i];
//printf("t suc dis[t] = %d\n",dis[t]);
if(!vis[t]){ //?
q.push(t);
vis[t] = 1;
}
}
}
}
return dis[T];
}
} G[17]; // 0 ~ 16
#define norm(x) ((x) ? 1 : 0)
int main(){
freopen("shortest.in", "r", stdin);
freopen("shortest.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i=1; i<=m; i++){
scanf("%d%d%d", &edges[i].f, &edges[i].t, &edges[i].c);
}
scanf("%d", &k);
for(int i=1; i<=k; i++){
int x; scanf("%d", &x); s[x] = 1;
}
int ans = inf;
for(int d=0; d<=16; d++){
int bit = 1 << d;
for(int i=1; i<=m; i++){ //add all edge
int f = (!s[ edges[i].f ]) ? edges[i].f : (n + 1 + norm(edges[i].f & bit)) ;
int t = (!s[ edges[i].t ]) ? edges[i].t : (n + 1 + norm(edges[i].t & bit)) ;
G[d].addedge(f, t, edges[i].c);
}
int t = min(G[d].SPFA(n+1, n+2), G[d].SPFA(n+2, n+1));
ans = min(ans, t);
}
printf("%d\n", ans);
return 0;
}
/*
5 6
1 2 1
2 3 3
3 1 3
2 5 1
2 4 2
4 3 1
3
1 3 5
*/
【2018.1.25】