JZOJ4858. 【GDOI2017模拟11.4】Walk

题目描述

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

分析

首先可以发现,边权只有1,那么直接bfs不用最短路。
怎么处理额外路径呢?暴力连边是 n2 的,然而我们发现,假设已经处理了点i的最短路,那么所有没有处理过的,满足and条件的点j都会被处理出来嘛。那么满足and条件的点就是满足在二进制下a[j]是a[i]删掉一些1的结果。同时我们又注意到,相同的a[j]处理一次就够了,如何避免a[j]被处理多次呢?
构建虚拟点,编号n+1~n+max(val_i),表示当真实点的权值为某个值x时,它的最短路是多少。那么我们把这些点加进图里面,每次走一条边就相当于删掉二进制下某个1,一起跑就行了,强制当真实点进入虚拟点要1的花费,出去则不用。
有一些细节,就是虚点优先跑,因为边权为0嘛。另外,如果边要全部建出来,会爆空间。其实对于虚拟点不用存边,因为你知道删去一个1后会是哪个点,根本不用存边。

代码

#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
const int N=200005,M=300005,er=1048576;
int b[M*4],next[M*4],first[N+er],tt;
int dis[N+er],n,m,x,y,a[N],i,j,k,l,dur,p,d[N+er],e[N+er],q1,q2,p1,p2,z,t;
void cr(int x,int y)
{
    tt++;
    b[tt]=y;
    next[tt]=first[x];
    first[x]=tt;
}
void bfs()
{
    fo(i,1,n+er) dis[i]=-1;
    dis[1]=0;
    d[1]=1;
    q1=0;
    q2=1;
    while (q1<q2)
    {
        x=d[++q1];
        for(p=first[x];p;p=next[p])
            if (dis[b[p]]==-1)
            {
                dis[b[p]]=dis[x]+1;
                d[++q2]=b[p];
            }
        if (dis[a[x]+n]==-1)
        {
            dis[a[x]+n]=dis[x]+1;
            e[1]=a[x]+n;
            p1=0;
            p2=1;
            while (p1<p2)
            {
                z=y=e[++p1];
                z-=n;
                for(p=first[y];p;p=next[p])
                if (dis[b[p]]==-1)
                {
                    dis[b[p]]=dis[x]+1;
                    d[++q2]=b[p];
                }
                while (z)
                {
                    t=y-((z)&(-z));
                    if (t==n) break;
                    if (dis[t]==-1)
                    {
                        dis[t]=dis[x]+1;
                        e[++p2]=t;
                    }
                    z-=z&(-z);
                }
            }
        }
    }
}
int main()
{
    freopen("walk.in","r",stdin);
    freopen("walk.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n) 
    {
        scanf("%d",a+i);
        cr(a[i]+n,i);
    }
    fo(i,1,m)
    {
        scanf("%d%d",&x,&y);
        cr(x,y);
    }
    bfs();
    fo(i,1,n) 
    {
        printf("%d\n",dis[i]);
    }
}

你可能感兴趣的:(二进制)