2018.1.24 T3 最短路径

调试了一下午终于调出来了,原来WA了九个点,竟然是因为“优雅”的建图打错了一个字母。。

最短路径

【题面】


题目描述

题目描述


输入格式

输入格式


输出格式

输出答案,为一个整数。


样例输入

样例输入


样例输出

2


数据范围

数据范围


【思路】

GQH大神太强了,现场想出标算。假如我们把那个有k个点的点集称为集合S,如果我们把这个集合分成两个互不相交的子集(并且使它们的并集为原集合) S1,S2 S 1 , S 2 ,然后分别对 S1 S 1 S2 S 2 进行缩点。

(缩点:用一个新点去代替原来集合中的所有的点,假如原来集合 S1 S 1 中有一个点 u u ,那么u的所有出边都要从这个“新点”连出, u u 的所有入边都要连入这个“新点”。当然了,要重建一个图,并且这张新图中将不存在一个叫u的点。)

很显然(数学老师好像很不喜欢学生写显然),如果最终的“答案路径”的起点和终点被划分到了不同的集合,那么缩点之后的两个集合间的最短路就是最终的答案。(因为是有向图,我们可以分别从两个“新点”跑一次SPFA,求出 S1S2,S2S1 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】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值