zoukankan      html  css  js  c++  java
  • 比特镇步行(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
    ",dis[i]);
    } 
    
  • 相关阅读:
    MySQL优化
    MySQL 的 SQL 操作
    笔记本电脑同时使用两个网络
    top
    logrotate
    正则表达式学习总结
    HttpClient parameter 和body 传输同时进行
    Node.js背景
    前后端分离的理解
    shiro 的subject 以及Context 对象的具体的含义。
  • 原文地址:https://www.cnblogs.com/Tieechal/p/11545479.html
Copyright © 2011-2022 走看看