比特镇步行(Walk)【虚点+bfs+dfs】

时间:2019-09-18
本文章向大家介绍比特镇步行(Walk)【虚点+bfs+dfs】,主要包括比特镇步行(Walk)【虚点+bfs+dfs】使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Online Judge:NOIP2016十连测第一场 T3

Label:虚点,bfs,dfs

题目描述

说明/提示

对于100%数据,\(n<=200000\)\(m<=300000\)\(val_i<=2^{20}\)

题解

既然是二进制,只有当\(val_j\)\(val_i\)的子集时,i有一条连向j的边。

1.先来看看如果只有原图,也就是不考虑两两连边时怎么得到每个点离源点1的距离?

很明显跑个最短路。但由于边权都为1,就不用跑\(dijkstra/spfa\)什么的了,直接一遍\(O(N)\)\(bfs\)就可以了。

2.考虑两两连边时怎么做?

如果直接两两判断能否连边是\(O(N^2)\),时间和空间都承受不了。

考虑设置虚点,也就是将边\(u->v\),转化为\(u->val_u->...->val_v->v\)。先别在意上面这些边的方向。基本思想就是利用这些二进制数作为中间媒介来模拟连边,根据\(val_i\)的数据范围,顶多只有\(2^{20}=1048576\)个数字,这看起来似乎比较可做。

\(dis[]\)表示每个点离源点1的距离,由于虚点相当于媒介,我们并不将其计入距离中,所以数组大小开\((N)\)。初始时将\(dis[]\)初始化为-1,源点\(dis[1]=0\)

设现在\(bfs\)的队首元素为\(x\)

按照原来的思路,设\(x\)两两连边时能连向实点\(y\)\(val_j∈val_i\)),如果之前没有访问过\(y\)(也就是\(dis[y]==-1\))则\(dis[y]=dis[x]+1\)。按照bfs的流程走,还应该将\(y\)加进队列末去。所以如何去在可行的时间内去模拟呢?直接套一个\(dfs\),去暴力枚举\(val_x\)的子集,当然如果真的这样做每次取出队首后,时间复杂度都是\(O(val_x的子集数)\),一定会T。回到刚才采取\(bfs\)来搜最短路的思路,如果某个点第一次被访问到,那当前这个准备更新的距离就是他到源点的最短路,所以直接记忆化一下,只有当当前二进制数没有被\(mark\)时才继续dfs子集。

这样每个二进制数最多只会被弄到一次。总的时间复杂度大致为\(O(2^{20}+N+M)\)

完整代码如下:

ps:下面代码中用\(e\)存实点与实点之间的原有有向边,用\(g\)\(val_i\)指向\(i\)的有向边。

#include<bits/stdc++.h>
using namespace std;
const int N=200010,M=300010;
inline int read(){
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x;
}
struct Edge{
    int to,nxt;
}e[M];//实点与实点 
vector<int>g[1<<20];//虚点连向实点 
int n,m,head[N],cnt;
inline void link(int u,int v){
    e[++cnt].to=v,e[cnt].nxt=head[u];
    head[u]=cnt;
}

queue<int>q;
int val[N],dis[N],mark[1<<20];
void boom(int now,int s,int lst){//解决子集 
    if(mark[now])return;
    mark[now]=1;
    for(int i=0;i<g[now].size();i++){
        int y=g[now][i];
        if(dis[y]==-1){
            dis[y]=dis[s]+1;
            q.push(y);
        }
    }
    for(int i=lst+1;i<=20;i++)if((1<<i)&now)boom(now^(1<<i),s,lst+1);       
}
void bfs(){
    memset(dis,-1,sizeof(dis));
    q.push(1);dis[1]=0;
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(dis[y]==-1){
                dis[y]=dis[x]+1;
                q.push(y);
            }
        }
        boom(val[x],x,-1);
    }
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++){
        val[i]=read();
        g[val[i]].push_back(i);
    }
    for(int i=1;i<=m;i++){
        int u=read(),v=read();
        link(u,v);
    }
    bfs();
    for(int i=1;i<=n;i++)printf("%d\n",dis[i]);
} 

原文地址:https://www.cnblogs.com/Tieechal/p/11545479.html