【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 0≤vali≤ 220

分析

直接连边显然会超时,考虑优化构图。
如果给每一个权值都建一个点,然后一个权值向能到达的其它权值连一条长度为0的边。接下来,每个街区向它的权值连一条长度为0的边,权值向街区连一条长度为1的边。显然跑出来的最短路是等价的。
对呀,等价的!
但是如果向能到达的其它权值都连边,就太多边了。只需向二进制下比它少一个1的连边即可。
因为边权只有0、1,跑最短路用bfs即可。注意:要先扩展以权值为0的边相连的点,并且是队列里dis[]相同的点一起扩展,这样才能保证队列中最短路长度是递增的。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int M=1<<20,N=200005,maxn=N+M;

typedef long long LL;

int val[N],tot,dis[maxn],Data[maxn],n,m,h[maxn],e[maxn],nxt[maxn];

bool v[maxn];

char c;

int read()
{
    for (c=getchar();c<'0' || c>'9';c=getchar());
    int x=c-48;
    for (c=getchar();c>='0' && c<='9';c=getchar()) x=x*10+c-48;
    return x;
}

int lowbit(int x)
{
    return x&-x;
}

void add(int x,int y)
{
    e[++tot]=y; nxt[tot]=h[x]; h[x]=tot;
}

void put(int x)
{
    if (!x) return;
    int i,j;
    for (i=x;i;i^=j)
    {
        j=lowbit(i);
        if (!v[x^j])
        {
            Data[++m]=x^j;
            v[x^j]=1;
            dis[x^j]=dis[x];
            put(x^j);
        }
    }
}

int main()
{
    freopen("walk.in","r",stdin); freopen("walk.out","w",stdout);
    n=read(); m=read();
    for (int i=1;i<=n;i++)
    {
        val[i]=read();
        add(val[i],i+M);
    }
    while (m--)
    {
        int x=read(),y=read();
        add(x+M,y+M);
    }
    Data[m=1]=M+1; v[M+1]=1;
    memset(dis,255,sizeof(dis));
    dis[M+1]=0;
    for (int i=1;i<=m;i++)
    {
        int j;
        for (j=i;j<=m && dis[Data[i]]==dis[Data[j]];j++);
        for (int k=i;k<j;k++)
        {
            int x=Data[k];
            if (x>M)
            {
                if (!v[val[x-M]])
                {
                    Data[++m]=val[x-M];
                    v[Data[m]]=1;
                    dis[Data[m]]=dis[x];
                    put(Data[m]);
                }
            }else put(x);
        }
        for (int k=i;k<j;k++)
        {
            int x=Data[k];
            for (int i=h[x];i;i=nxt[i]) if (!v[e[i]])
            {
                v[e[i]]=1;
                Data[++m]=e[i];
                dis[e[i]]=dis[x]+1;
            }
        }
        i=j-1;
    }
    for (int i=1;i<=n;i++) printf("%d\n",dis[i+M]);
    fclose(stdin); fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值