Walk

题目描述

在比特镇一共有n 个街区,编号依次为1 到n,它们之间通过若干条单向道路连接。

比特镇的交通系统极具特色,除了m 条单向道路之外,每个街区还有一个编码vali,不同街区可能

拥有相同的编码。如果val_i and val_j = val_j,即val_i 在二进制下与val_j 做与运算等于val_j,那么也会

存在一条额外的从i 出发到j 的单向道路。

Byteasar 现在位于1 号街区,他想知道通过这些道路到达每一个街区最少需要多少时间。因为比特

镇的交通十分发达,你可以认为通过每条道路都只需要1 单位时间。

优化连边

我们可以暴力连边,然后广搜一遍,但显然要T。
我们尝试用分治法优化连边,不改变原图的等价性。
先对每种权值建一个点。
solve(l,r)表示把[l,r]内的边连了。
递归处理solve(l,mid)和solve(mid+1,r)
然后左右区间对应的权值连边,大的向小的连0。
对于每个原先的节点i,i向ai连0,ai向i连1。原本存在边的节点i和j,i向j连1。
易证这没有改变原图的等价性。
现在有0有1,那么每次一个元素加入队尾时,要把其所有连出去的0边没有访问过的元素也加入队尾。
为了优化常数,可以先连1边再连0边。
具体实现看代码。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=200000+10,maxd=20,maxtot=maxn+(1<<maxd)+10,maxm=300000+10,maxtop=11185800;
bool dis[maxtop];
int h[maxtot],next[maxtop],go[maxtop],f[maxtot],a[maxn];
int dl[maxtot];
int i,j,k,l,t,n,m,tot,top,head,tail,now;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y,int z){
    go[++top]=y;
    dis[top]=z;
    next[top]=h[x];
    h[x]=top;
}
void solve(int l,int r){
    if (l==r) return;
    int i,mid=(l+r)/2;
    solve(l,mid);solve(mid+1,r);
    fo(i,mid+1,r) add(n+i+1,n+(i-(mid+1-l))+1,0);
}
void insert(int x){
    dl[++tail]=x;
    int t=h[x];
    while (t){
        if (dis[t]==1) break;
        if (f[go[t]]==-1){
            f[go[t]]=f[x];
            insert(go[t]);
        }
        t=next[t];
    }
    h[x]=t;
}
int main(){
    freopen("walk.in","r",stdin);freopen("walk.out","w",stdout);
    n=read();m=read();
    tot=n+(1<<maxd);
    fo(i,1,n) a[i]=read();
    fo(i,1,m){
        j=read();k=read();
        add(j,k,1);
    }
    fo(i,1,n) add(n+a[i]+1,i,1);
    fo(i,1,n) add(i,n+a[i]+1,0);
    solve(0,(1<<maxd)-1);
    fo(i,1,tot) f[i]=-1;
    f[1]=0;
    dl[tail=1]=1;
    while (head<tail){
        k=dl[++head];
        t=h[k];
        while (t){
            if (f[go[t]]==-1){
                f[go[t]]=f[k]+dis[t];
                insert(go[t]);
            }
            t=next[t];
        }
    }
    fo(i,1,n) printf("%d\n",f[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值