在比特镇一共有 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\n",d[i+cnt]);
return 0;
}