Walk

在比特镇一共有 n 个街区,编号依次为 1 到 n,它们之间通过若干条单向道路连接
比特镇的交通系统极具特色,除了 m 条单向道路之外,每个街区还有一个编码 vali,不同街区可能拥有相同的编码。如果 vali and valj = valj,即 vali 在二进制下与 valj 做与运算等于 valj,那么也会存在一条额外的从 i 出发到 j 的单向道路
Byteasar 现在位于 1 号街区,他想知道通过这些道路到达每一个街区最少需要多少时
因为比特镇的交通十分发达,你可以认为通过每条道路都只需要 1 单位时间

Input
第一行包含两个正整数 n;m,表示街区的总数以及道路的总数
第二行包含 n 个正整数 val1; val2; ……; valn,分别表示每个街区的编码
接下来 m 行,每行包含两个正整数 ui; vi,表示一条单向道路,起点为 ui,终点为 vi
Output
输出 n 行,每行一个整数,其中第 i 行输出到达第 i 个街区的最少时间,如果无法到达则输出 -1

Examples
walk.in
5 2
5 4 2 3 7
1 4
2 3

walk.out
0
1
2
1
-1

分析:
考场上dij,稳妥40

看一下官方题解:
依旧考虑新增 2^20个点
i只需要向i去掉某一位的1的点连边
这样一来图的边数就被压缩到了 20*2^20+2n+m,然后
BFS求出1到每个点的最短路即可
时间复杂度 O(20*2^20+n+m)

第一眼看到这个,我的内心:mmp,ta在说什么。。。

解释一下:
特殊边的处理是非常恶心的,因为我们不能枚举判定,也不能直接相连(n^2),所以需要考虑转化
新增 2^20个点:不知道为什么是这么多。。。
i只需要向i去掉某一位的1的点连边,边权是0:
假设是第11个点,
11的二进制是1011,
也就是说,11号点会向1010(10),0011(3),1001(9)三个点连线
这里写图片描述
我们可以通过走多条这种有向边的方式到达他的所有子集点,即i&j=j的点。
对于原来的n个点,先把m条边连好,连权值为1的有向边,
然后对于i号点,由它向新增的第val[i]个点(就是他权值所对应的点)连一条权值为1的有向边
再由新增的第val[i]个点向它连一条权值为0的有向边。
BFS 的时候,每次要把用0权值的边连接的所有点都加入队尾,以保证距离不降

这里写代码片
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>

using namespace std;

const int INF=0x33333333;
const int N=300010;
int n,m,cnt;
struct node{
    int x,y,v,nxt;
};
node way[N<<2];
int st[N+(1<<20)],tot=0,d[N+(1<<20)];
queue<int> q;

void add(int u,int w,int z)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].v=z;
    way[tot].nxt=st[u];st[u]=tot;
}

//把所有与x相连的边权为0的点加入队列 
void dfs(int x,int len)  //len是路径长度 
{
    if (d[x]>=0) return;  //已经遍历过了 
    q.push(x);
    d[x]=len;
    for (int i=st[x];i;i=way[i].nxt)
        if (way[i].v==0)  //费用是0 
           dfs(way[i].y,len);
    if (x>=cnt) return;
    for (int i=0;i<20;i++)  
        if (x>>i&1)  //二进制第i位上是1 
           dfs(x^(1<<i),len);  //按位异或,相同是0,不同是1 
//其实这一部分的dfs就相当于从val向与val差1的数连边,节省了连边的时间和空间 
}   

int main()
{
    //freopen("walk.in","r",stdin);  
    //freopen("walk.out","w",stdout);
    scanf("%d%d",&n,&m);
    cnt=1<<20;  //2^20个增加的点 
    for (int i=1;i<=n;i++)   
    {
        int x;
        scanf("%d",&x);
        add(i+cnt,x,1);  //+cnt把前面需要增加的点的位置空出来 
        add(x,i+cnt,0);
    }
    for (int i=1;i<=m;i++)
    {
        int u,w;
        scanf("%d%d",&u,&w);
        add(u+cnt,w+cnt,1);
    }
    for (int i=0;i<=cnt+n;i++) d[i]=-1;
    dfs(cnt+1,0);
    while (!q.empty())  //bfs 
    {
        int now=q.front();
        q.pop();
        for (int i=st[now];i;i=way[i].nxt)
            if (way[i].v==1)  //距离++ 
               dfs(way[i].y,d[now]+1);
    }
    for (int i=1;i<=n;i++)
        printf("%d
",d[i+cnt]);
    return 0;
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673531.html