bitset在图论上的应用 传递闭包【例题gym 100342J & gym 100345H 】

bitset优点:

bitset在某些常数优化以及状态保存方面被称之为神器并不为过,主要表现在以下几个方面:
1. 状态表示。试想,用一个数来表示状态的极限是64位,而bitset可以保存任意位二进制数,并且修改简单,统计方便,并且支持批量操作。
2. 常数优化。图论的题,尤其涉及不带权的邻接图,算法经常动辄 n2,n3 ,这个时候我们可以用n个bitset存储每个点的邻接情况,并进行相应位操作,来替代直接遍历图的操作,经常可以将总复杂度降低为原来的1/32甚至还要少!
3. 集合运算。交集按位与,并集按位或,取反直接flip,简直好用。
4. 存储优化。一个bool型变量占一个字节(1byte),而bitset的一位只占1bit!某些内存卡得特别紧的情况可以试试(一般无卵用=_=

bitset基本操作表:


下面通过gym上的两道例题来了解一下bitset在图论上的应用


传送门:gym 100342J Triatrip

题意:

给一个邻接矩阵,求有多少条路径可以由A出发,经过B ,再经过C,最后回到A。

思路:

无论是用邻接矩阵乘法,还是跑flyod,都是 n3 的算法,而n≤1500,直接来果断TLE。
路径为A→B→C→A,是一个三元环,我们枚举每一条B→C路径,如果用n个bitset存储每一个点的出度与入度情况,然后将B的入度与C的出度相与,统计相与后的结果有多少个1,便是有多少个三元环,累计相与结果,答案即为累计值/3,复杂度下降为O(n2∗n/32),可以卡着时间过。
之所以要除以3的原因是环A→B→C→A包含了B→C→A→B,与C→A→B→C。

代码:

#include <bits/stdc++.h>
using  namespace  std;

#define ll __int64
#define rep(i,k,n) for(int i=k;i<=n;i++)

const int N=1510;

char s[N][N];
bitset<N>bit1[N], bit2[N], tmp;
int n;

int  main(){
  freopen("triatrip.in","r",stdin);//必须加上,不然得WA
  freopen("triatrip.out","w",stdout);
  scanf("%d", &n);
  rep(i, 0, n-1){
    scanf("%s", s[i]);
    rep(j, 0, n-1){
      if(s[i][j] == '+')bit1[i].set(j), bit2[j].set(i);
    }
  }

  ll ans=0;
  rep(i, 0, n-1){
    rep(j, 0, n-1){
      if(bit1[i][j]){
        tmp = bit1[j] & bit2[i];
        ans += tmp.count();
      }
    }
  }
  printf("%I64d\n", ans / 3);
  return 0;
}


传送门:gym 100345H Settling the Universe Up

题意:

输入一个邻接矩阵,统计图中有多少对点 ( u , v ) 是从u出发可以到达v,且u < v, 单向。
同时动态增删图中路径。


思路:

这题用线段树维护起来有些麻烦
先用bitset1存储每个点一次可到达点。
然后新开一组bitset2存储每个点的所有升序可到达点,bitset2可由bitset1进行或运算推出来,每次更新都重新推一次,直接暴力即可。
不得不说bitset实在神奇。。不用bitset我都不知道怎么做这道题了。

代码:

#include <bits/stdc++.h>
#define rep(i,k,n) for(int i=k;i<=n;i++)
using  namespace  std;

template<class T> void read(T&num) {
    char CH; bool F=false;
    for(CH=getchar();CH<'0'||CH>'9';F= CH=='-',CH=getchar());
    for(num=0;CH>='0'&&CH<='9';num=num*10+CH-'0',CH=getchar());
    F && (num=-num);
}
int stk[70], tp;
template<class T> inline void print(T p) {
    if(!p) { puts("0"); return; }
    while(p) stk[++ tp] = p%10, p/=10;
    while(tp) putchar(stk[tp--] + '0');
    putchar('\n');
}

const int N=210;

int n, m, k, sum;
bitset<N>b[N], bit[N];

void update(){
  rep(i, 1, n)bit[i].reset();
  sum=0;
  for(int i=n; i>=1; i--){
    bit[i].set(i);
    for(int j=1; j<i; j++){
      if(b[j][i])
        bit[j] |= bit[i];
    }
    sum += bit[i].count();
  }
}

int  main(){
  freopen("settling.in", "r", stdin);
  freopen("settling.out", "w", stdout);
  read(n),read(m);
  rep(i, 1, m){
    int u, v;
    read(u),read(v);
    b[u].set(v);
  }
  update();
  print(sum-n);//总数减去自环

  read(k);
  while(k--){
    char op[2];int u, v;
    scanf("%s%d%d",op, &u, &v);
    if(op[0] == '+'){
      b[u].set(v);
      update();
      print(sum-n);
    }
    else if(op[0] == '-'){
      b[u].reset(v);
      update();
      print(sum-n);
    }
    else{
      if(bit[u].test(v))puts("YES");
      else puts("NO");
    }
  }
  return 0;
}


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
传递闭包是一个用于描述关系的概念,其中对于集合中的任意两个元素,如果存在一条路径可以从一个元素到达另一个元素,则称该关系具有传递性。在计算机科学中,传递闭包常常用于优化算法和数据结构。 Bitset是C++标准库中的一个类,它可以被用来表示一个固定大小的位集合。Bitset提供了一组操作,如设置某一位的值、获取某一位的值、进行位运算等。在优化传递闭包时,可以使用bitset来表示关系的传递闭包。 具体的优化方法如下: 1. 初始化bitset:首先,创建一个大小为n的bitset,其中n是关系中元素的数量。将所有的位初始化为0。 2. 构建初始关系:根据关系的定义,将bitset中与初始关系有关的位设置为1。例如,如果存在一个关系R中的元素i到元素j的边,则将bitset中的第(i*n+j)位设置为1。 3. 计算传递闭包:使用Floyd-Warshall算法计算传递闭包。通过遍历所有的中间节点k和所有的节点i和j,如果bitset中的第(i*n+k)位和第(k*n+j)位都为1,则将bitset中的第(i*n+j)位设置为1。 4. 查询传递闭包:在计算完传递闭包后,可以使用bitset的位操作来快速查询关系的传递性。例如,可以通过检查bitset中的第(i*n+j)位是否为1来判断元素i是否可以到达元素j。 使用bitset来优化传递闭包可以有效地减少内存消耗和提高运算效率,尤其在处理大规模数据时尤为明显。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值