NC15479 最短路 带证明(位运算+最短路)

题目描述
企鹅国中有N座城市,编号从1到N。
对于任意的两座城市i和j,企鹅们可以花费(i xor j)*C的时间从城市ii走到城市jj,这里C为一个给定的常数。
当然除此之外还有M条单向的快捷通道,第i条快捷通道从第F个城市通向第T个城市,走这条通道需要消耗V
​的时间。
现在来自Penguin Kingdom University的企鹅豆豆正在考虑从城市A前往城市B最少需要多少时间?

输入描述:
输入第一行包含三个 整数N,M,C,表示企鹅国城市的个数、快捷通道的个数以及题面中提到的给定的常数C。
接下来的M行,每行三个正整数Fi,Ti,Vi(1≤Fi≤N,1≤Ti≤N,1≤Vi≤100),分别表示对应通道的起点城市标号、终点城市标号和通过这条通道需要消耗的时间。
最后一行两个正整数A,B(1≤C≤100),表示企鹅豆豆选择的起点城市标号和终点城市标号。
输出描述:
输出一行一个整数,表示从城市 A 前往城市 B 需要的最少时间。
示例1
输入

4 2 1
1 3 1
2 4 4
1 4

输出

5

说明
直接从 1 走到 4 就好了。
示例2
输入

7 2 10
1 3 1
2 4 4
3 6

输出

34

说明
先从 3 走到 2 ,再从 2 通过通道到达 4 ,再从 4 走到 6。
思路:
思路参考:https://blog.nowcoder.net/n/3cb5429047ce456a811e65c458c01052
但是大佬的代码依旧有问题,即24行i应该从0开始循环,已在本题中改正、
有两个重要的点:

  • 不能无脑加边
  • i需要从0开始遍历

针对第一个问题大家直接看大佬的解释就好。
针对第二个问题做如下解释:
若n 为4且不存在快速单向边的情况,如果i从1开始,那么就不会存在某个点与4差一位,即没有边到达4。
而若想与4连边,即与4差一位,只能是0,所以0一定要存在。

这个时候会产生两个疑问,第一个疑问是加入了0点,会不会出现“经过了0的中转导致两点间的距离变小”从而出现了错误的结果第二个疑问是如果某个点想到达点2n,经过0的中转,会不会导致这个点到点2n的距离变大呢?
归结起来可以总结为,这种操作是否会造成距离的缩小或者增大。
针对第一个疑问的证明如下:
设求点u到点v的距离,经过0的中转,两点的距离可以表示为 u ⨁ 0 + 0 ⨁ v 即 u + v u⨁0 + 0⨁v即u+v u0+0vu+v而已知 u + v > = u ⨁ v u+v>=u⨁v u+v>=uv,故不会造成距离的缩小。
针对第二个疑问证明如下:
先说结论:设某个点为u,标号为 2 n 2^n 2n的点标号为v,那么u+v=u⨁0 + 0⨁v
为什么这个结论一定会成立呢?
已知若两个数按照二进制排列,若每一位上的数都满足(一个是1一个是0)或者(都为0)在这个前提下,u⨁v=u+v一定成立,例如(10011)⨁(01000)=(11011)换算成十进制为27,而19+8=27。
综上,如果n为2的幂次,那么如果不添加0为中转点,就会导致n点无法到达。如果添加了0作为中转点,那么每一个到达0的点,都可以到达n点,且经过中转的距离与直接到达n的距离相等。

代码:

#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e6+10;
int e[N],ne[N],w[N],h[N],idx=0;
typedef pair<int,int>PII;
int n,m,c;
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
    w[idx]=c;
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
int dijkstra(int s,int t)
{
    memset(dist, 0x3f, sizeof dist);
    dist[s] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, s});      // first存储距离,second存储节点编号

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second, distance = t.first;

        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    return dist[t];
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m>>c;
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    for(int i=0;i<=n;i++)
    {
        for(int j=1;j<=n;j<<=1)
        {
            int u=i;
            int v=i^j;
            int w=j*c;
            if(v>n)
            {
                continue;
            }
            add(u,v,w);
        }
    }
    int s,t;
    cin>>s>>t;
    cout<<dijkstra(s,t);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值