算法--洛谷P6328我是仙人掌--BFS求最短路+bitset求点集

#新星杯·14天创作挑战营·第11期#

题目描述

给定n,m,p表示给定一个有n个节点的图,接下来m行输入,每行给出a,b表示a,b节点之间有一条边,接下来p次询问,每次询问先给定a,表示有a行输入,每行输入有序对<u,d>,表示询问图中满足到节点u的距离小于等于d的节点数量,每次询问有a个有序对,表示询问图中至少满足其中一种要求的点的数量

  • 输入样例
5 6 6
2 3
1 3
2 5
1 3
3 2
2 5
1
3 1
1
1 1
1
1 4
1	
5 2
1
1 4
2
1 0
5 1
  • 输出样例
3
2
4
3
4
3

实现方法

基本思路是通过vector邻接表存图(可能是稀疏图,时间复杂度可能更优,因为不需要遍历完所有节点),首先通过BFS计算出图上从一个节点到其他节点之间的距离,也即BFS求任意点对之间的最短路,然后感觉其实可以存一个dis[i][j]表示节点i到节点j之间的距离,然后接下来怎么写?对于一个询问<u,d>,从0~n的节点上循环遍历dis数组,时间复杂度是O(n)的,考虑到p次询问和a个要求,为O(pan),再加上BFS的时间复杂度,一次BFS的时间复杂度是多少?邻接表实现的BFS的时间复杂度是O(n+m)的,因为外层队列循环会遍历所有图上节点n,当遍历到一个节点时,会遍历每个节点的边,所有节点的边数和为m(有向图)或2m(无向图),那么对于每个节点都进行一次BFS,时间复杂度为O(n*(n+m)),整体来看感觉可行,尝试写一下这种思路,

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
void BFS(int x,vector<vector<int>> &dis,vector<vector<int>> &g){
    queue<int> q;
    q.push(x);
    dis[x][x]=0;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=0;i<g[u].size();i++){
            int v=g[u][i];
            if(dis[x][v]==-1){
                dis[x][v]=dis[x][u]+1;
                q.push(v);
            }
        }
    }
}
int main(){
    int n,m,p;cin>>n>>m>>p;
    vector<vector<int>> g(n+1,vector<int>());
    for(int i=0;i<m;i++){//节点编号1~n
        int a,b;cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    vector<vector<int>> dis(n+1,vector<int>(n+1,-1));
    for(int i=1;i<=n;i++){
        BFS(i,dis,g);
    }
    for(int i=0;i<p;i++){
        int a;cin>>a;
        int cnt=0;
        vector<bool> b(n+1,false);
        for(int j=0;j<a;j++){
            int u,d;cin>>u>>d;
            for(int k=1;k<=n;k++){
                if(dis[u][k]<=d&&dis[u][k]!=-1&&b[k]==false)cnt++;
            }
        }
        cout<<cnt<<endl;
    }
}

写完,提交,一气呵成,TLE了???
算法的时间复杂度太高了
O ( n ∗ ( n + m ) + p ∗ a ∗ n ) O(n*(n+m)+p*a*n) O(n(n+m)+pan)
可以看到原题中数据范围点的个数最多只有1000个,但p和a的最大值分别达到了 10 6 10^6 106 10 5 10^5 105的数据量,这表明查询过程会耗费大量的时间

考虑一下怎么优化,刚一开始我们想的是用bfs求出来两点间的最小距离,并且用数组保存这个最小距离,最后查询,这种模式显然不够优

这里我们引入一种效率更高的数据结构:
b i t s e t < M A X N > bitset<MAXN> bitset<MAXN>
bitset一般能够实现用位运算代替一些存储结构,比如我们考虑使用bitset来储存到某点x的距离为d的点,那么答案就是一堆bitset取或得到的点

定义bitset<N> f[i][j],那么我们就可以用f[u][d][v]=1表示点v到点u的距离为d,在bfs的过程中,标记当前读到的点的dis对应的bitset,例如f[ x x x][ d i s i dis_{i} disi][ i i i]=1表示当前点i距离起点x的距离为d,因为储存在bitset数组里,如果我们枚举点和x之间的距离为0~n,这时候再把每次枚举出来的bitset数组或起来,就得到了一个表示到x的距离小于等于d的bitset了,这里面每一个取值为1的位都表示满足条件的一个点

接下来我们尝试使用bfs实现这个想法

#include<iostream>
#include<vector>
#include<queue>
#include<set>
#include<bitset>
#define N 1005 
using namespace std;
bitset<N> f[N][N];
void bfs(int x,int n,vector<vector<int>> &g){
    queue<int> q;
    vector<int> dis(n+1,-1);
    dis[x]=0;
    f[x][0][x]=1;
    q.push(x);
    while(!q.empty()){
        int u=q.front();q.pop();
        for(auto i=g[u].begin();i!=g[u].end();i++){
            int v=*i;
            if(dis[v]==-1){
                q.push(v);
                dis[v]=dis[u]+1;
                f[x][dis[v]][v]=1;
            }
        }
    }
    for(int d=1;d<=n;d++){
        f[x][d] |= f[x][d-1];
    }

}
int main(){
    int n,m,p;
    scanf("%d %d %d",&n,&m,&p);
    vector<vector<int>> g(n+1,vector<int>());
    vector<vector<bool>> bl(n+1,vector<bool>(n+1,0));//这个要有初始化,因为为了避免if判断的时候访问到了不可访问的下标
    for(int i=0;i<m;i++){
        int x,y;
        scanf("%d %d",&x,&y);
        if(!bl[x][y]){
            g[x].push_back(y);
            g[y].push_back(x);
            bl[x][y]=1;
            bl[y][x]=1;
        }
        
        
    }
    for(int i=1;i<=n;i++){
        bfs(i,n,g);
    }
    for(int i=0;i<p;i++){
        int a;
        scanf("%d",&a);
        bitset<N> result(0);
        for(int j=0;j<a;j++){
            int u,d;
            scanf("%d %d",&u,&d);
            if(d>n)d=n;
            result|=f[u][d];
        }
        cout << result.count() << endl;
    }
    
}

这里在写的时候有很多细节,比如刚读入一个图的时候为了避免重复存边,可以使用一个二维的vector的bool数组判断之前是否已经加入过了,在数据规模很大的时候使用scanf确实会有明显的速度提升,里面还有一句if(d<n)d=n;感觉这个就考虑的挺多的,如果d真的大于n的话,这个还能避免数组访问越界,总体感觉用的最好的是bitset,使用bitset维护一个点集并且求点集的数量,查询的时候会节省很多时间


写在最后
博主还是学生
目前准备用写博客的方式记录自己的学习生活
希望能得到大家的认可
准备写学习算法和机器学习的相关知识
也准备写一些读书笔记
希望能帮助到你
我们一起成长

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值