【NOI P模拟赛】寻找道路(bfs,最短路)
阅读原文时间:2023年07月10日阅读:1

题面

一道特殊的最短路题。

给一个

n

n

n 个点

m

m

m 条有向边的图,每条边上有数字

0

\tt0

0 或

1

\tt1

1 ,定义一个路径的长度为这个路径上依次经过的边上的数字拼在一起后在二进制下的值(前导

0

\tt0

0 对该路径长度没有贡献)。现在需要你求出从

1

1

1 号点出发,到

2

n

2\sim n

2∼n 号点的路径的长度的最小值。

n

1

0

6

,

m

2

1

0

6

.

n\leq10^6,m\leq2\cdot10^6.

n≤106,m≤2⋅106.

题解 & CODE

我们考虑该二进制路径的特性,前导

0

\tt0

0 是没有贡献的,因此

1

1

1 号点能直接通过

0

\tt0

0 走到的点肯定先处理出来。然后,路径上就必定有一个

1

\tt1

1 了,第一个

1

\tt1

1 的位数越高,路径长度就一定越大,所以接下来可以基于路径长度来简化该最短路。

我们根据首个

1

1

1 的位数来对图分层,此时进行 bfs,每次把该层的所有点拿出来,拿出来的时候就按照路径从小到大站成一列了,然后从前到后扩张每个点,优先走

0

0

0 边。这样可以线性地得到下一层所有点的路径长度,以及顺便把下一层的点按照路径长度从小到大放进了队列里。

时间复杂度

O

(

n

+

m

)

O(n+m)

O(n+m) 。


笔者的第二种方法仅供娱乐。

考虑取模过后(哈希过后)失去相对大小信息,无法比较,高精度又太慢。这个时候就可以用离散化

我们对

D

i

j

k

s

t

r

a

\rm Dijkstra

Dijkstra 进行改进,在求最短路的同时,求出每个点路径长度的排名(长度相等排名相同)。然后我们就可以依次比较 转移前驱的排名转移边 来对未确定的点进行择优,以及确定新排名时判断重复。

代码简单,只需要将传统 手写堆 优化

D

i

j

k

s

t

r

a

\rm Dijkstra

Dijkstra 进行小小的翻修。

时间复杂度

O

(

n

log

n

)

O(n\log n)

O(nlogn) 。输麻了

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<random>
#include<vector>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 2000005
#define LL long long
#define DB double
#define lowbit(x) (-(x) & (x))
#define ENDL putchar('\n')
#define FI first
#define SE second
int xchar() {
    static const int maxn = 100000;
    static char b[maxn];
    static int len = 0,pos = 0;
    if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
    if(pos == len) return -1;
    return b[pos ++];
}
//#define getchar() xchar()
LL read() {
    LL f=1,x=0;int s = getchar();
    while(s < '0' || s > '9') {if(s<0) return -1;if(s=='-')f = -f;s = getchar();}
    while(s >= '0' && s <= '9') {x=x*10+(s^48);s = getchar();}
    return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar('0'+(x%10));}
void putnum(LL x) {
    if(!x) {putchar('0');return ;}
    if(x<0) {putchar('-');x = -x;}
    return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 1000000007;
int n,m,s,o,k;
int hd[MAXN],v[MAXN<<1],nx[MAXN<<1],w[MAXN<<1],cne;
void ins(int x,int y,int z) {
    nx[++ cne] = hd[x]; v[cne] = y; w[cne] = z; hd[x] = cne;
}
int dp[MAXN],rk[MAXN];
int pr[MAXN],nw[MAXN];
int mg(int a,int b) {
    if(pr[a] == pr[b]) return nw[a] < nw[b] ? a:b;
    return pr[a] < pr[b] ? a:b;
}
int tre[MAXN<<1];
void upd(int x,int y) {
    tre[n+x] = y;
    for(int s = (n+x)>>1;s>0;s >>= 1) {
        tre[s] = mg(tre[s<<1],tre[s<<1|1]);
    }return ;
}
int main() {
    freopen("path.in","r",stdin);
    freopen("path.out","w",stdout);
    n = read();m = read();
    for(int i = 1;i <= m;i ++) {
        s = read();o = read();k = read();
        ins(s,o,k);
    }
    for(int i = 0;i <= n;i ++) {
        pr[i] = 0x3f3f3f3f; nw[i] = 1;
        dp[i] = -1;
    }
    dp[1] = 0; pr[1] = 0; nw[1] = 0;
    upd(1,1);
    int t = 0;
    for(int i = 1;i <= n;i ++) {
        int tt = tre[1];
        if(!tt) break;
        if(t && (pr[tt] != pr[t] || nw[tt] != nw[t])) rk[tt] = rk[t] + 1;
        else rk[tt] = rk[t];
        t = tt;
        for(int j = hd[t];j;j = nx[j]) {
            int y = v[j],z = w[j];
            if(rk[t] < pr[y] || (rk[t]==pr[y] && z < nw[y])) {
                pr[y] = rk[t]; nw[y] = z;
                dp[y] = (dp[t]<<1|z) % MOD;
                upd(y,y);
            }
        }
        upd(t,0);
    }
    for(int i = 2;i <= n;i ++) {
        AIput(dp[i],i==n ? '\n':' ');
    }
    return 0;
}