算法训练营 训练 集合运算(位向量bitset)

位向量:bitset

bitset是一个多位二进制数,如同状态压缩的二进制数。使用bitset时,需要引入头文件#include <bitset>bitset<1000> s表示定义一个1000位的二进制数s
基本的位运算~(取反)、&(与)、|(或)、^(异或)、>>(右移)、<<(左移)、==(相等比较)、!=(不相等比较)。
可以通过“[]”操作符直接得到第k位的值,也可以通过赋值操作改变该位的值。例如s[k] = 1,表示将二进制数s的第k位,置1。需要注意的是,最右侧为低位第0位,左侧为高位。1000位的二进制数,位序自右向左是0~999。

  • count():统计有多少位是1。
  • any():若至少有一位是1,则返回true
  • none():若没有为是1,全为0,则返回true
  • set():将所有位,置1。
  • set(k):将第k位,置1。
  • set(k,val):将第k位的值改为val,即s[k] = val
  • reset():将所有位,置0。
  • reset(k):将第k位置0,即s[k] = 0
  • flip():将所有位取反。
  • flip(k):将第k位取反。
  • size():返回大小(位数)。
  • to_ulong():返回它转换为unsigned long的结果,如果超出范围,则报错。
  • to_string():返回它转换为string的结果。

bitset定义和初始化

bitset的构造函数

bitset<n> b;//b有n位,每位都为0
bitset<n> b(u);//b是unsigned long型u的一个副本
bitset<n> b(s);//b是string对象s中含有的位串副本
bitset<n> b(s, pos, n);//b是s中从位置pos开始的n位副本

在定义bitset时,要明确bitset有多少位,必须在尖括号内给出它的长度值,给出的长度值必须是常量表达式。bitset<32> bitvec表示定义bitvec为32位的bitset对象,bitvec的位序自右向左为0~31。

用string对象初始化bitset对象

当用string对象初始化bitset对象时,string对象直接被表示为位模式。从string对象读入位集的顺序是从右向左。

string strval("1100");
bitset<32> bitv(strval);

bitv的位模式中,第2,3位被置为1,其余位置都被置为0。如果string对象的字符个数小于bitset类型的长度,则高阶位将被置为0。
注意string对象和bitset对象之间是反向转化的:string对象的最右边字符(即下标最大的字符)用来初始化bitset对象的低阶位(即下标为0的位)。
也可以用某个子串:

string str("110000100010");
bitset<32> bitv(str, 5, 4);//从str[5]开始取4位,即0001
bitset<32> bitvs(str, str.size()-4);//取末尾4位,即0010

bitv(str, 5, 4)表示从str[5]开始取4个字符初始化bitv。如果省略第3个参数,则表示取从开始位置一直到string末尾的所有字符。bitvs(str, str.size()-4)表示取出str末尾的4位来对bitvs的低4位进行初始化操作。

bitset上的操作

any/none

如果在对象中有一个或多个二进制位被置为1,则any操作返回true,否则返回false;相反,如果bitset对象中的二进制位全为0,则none操作返回true

bitset<32> bitvec;//32位,将所有位都置为0
bool is_set = bitvec.any();//所有位为0,返回false
bool is_not_set = bitvec.none();//所有位为0,返回true

count/size

可以使用count操作统计二进制位为1的个数:

size_t bits_set = bitvec.count();

count操作的返回类型是标准库中命名为size_t的类型,与vectorstring中的size操作一样,bitsetsize操作返回bitset对象中二进制位的个数,返回值的类型是size_t

size_t bits_set = bitvec.size()

set/test

用下标操作符或写某个索引位置的二进制位。

for (int index = 0; index != 32;index+=2){//把bitvec中的偶数下标的位都置为1
	bitvec[index] = 1;
}

除了用下标操作符,还可以用set设置给定二进制位的值。

for (int index = 0; index != 32;index+=2){//把bitvec中的偶数下标的位都置为1
	bitvec.set(index);
}

为测试某个二进制位是否为1,可以用test操作或者下标操作符。如果测试的二进制位为1,则返回true,否则返回false

if(bitvec.test(i)){
	;
}
if(bitvec[i]){
	;
}

set/reset

setreset操作分别用来对整个bitset对象的所有二进制位都置1和都置0。

bitvec.set();//都置为1
bitvec.reset();//都置为0

flip

flip操作可以对bitset对象的所有位或特定位按位取反。

bitvec.flip(0);//0位取反
bitvec[0].flip();//0位取反
bitvec.flip();//所有位取反

十进制转换二进制

cout << bitset<x>(y);//输出y转化为二进制后的数,共x位,不足补0,高位舍去
cout << bitset<5>(12) << endl;//输出01100

题目描述

给定 N N N个集合,第 i i i个集合 S i S_i Si C i C_i Ci个元素(集合可以包含两个相同的元素)。集合中的每个元素都用1~10000的正数表示。查询两个给定元素i和j是否同时属于至少一个集合。换句话说,确定是否存在一个数字 k ( 1 ≤ k ≤ N ) k(1 \leq k \leq N) k(1kN),使得元素 i i i和元素 j j j都属于 S k S_k Sk
输入:输入的第1行包含一个整数 N ( 1 ≤ N ≤ 1000 ) N(1 \leq N \leq 1000) N(1N1000),表示集合的数量。第 2 ∼ N + 1 2 \sim N+1 2N+1行,每行都以数字 C i ( 1 ≤ C i ≤ 10000 ) C_i(1 \leq C_i \leq 10000) Ci(1Ci10000)开始,后面有 C i C_i Ci个数字,表示该集合中的元素。第 N + 2 N+2 N+2行包含一个数字 Q ( 1 ≤ Q ≤ 200000 ) Q(1 \leq Q \leq 200000) Q(1Q200000),表示查询数。接下来的 Q Q Q行,每行都包含一对数字 i i i j j j ( 1 ≤ j , j ≤ 10000 (1 \leq j,j \leq 10000 (1j,j10000 i i i可以等于 j j j),表示待查询的元素。
输出:对于每个查询,如果存在这样的数字 k k k,则输出 Y e s Yes Yes,否则输出 N o No No

算法设计

查询两个元素是否同属于一个集合(至少一个)。所属集合可以用二进制表示法。
输入样例:

3			//表示3个集合
3 1 2 3		//表示第一个集合包含3个元素1、2、3
3 1 2 5		//表示第二个集合包含3个元素1、2、5
1 10		//表示第三个集合包含1个元素10

每个元素都可以用一个二进制数记录所属的集合。最右侧为低位0位,自右向左。例如1属于第1个集合,就将1对应的二进制数的第1位置为1,即s[1] = 0010;1还属于第2个集合,就将1对应的二进制数的第2位置为1,即s[1] = 0110;s[1] = 0110表示元素1属于1、2两个集合。同理,s[2] = 0110,s[3] = 0010,s[5] = 0100,s[10] = 1000。

4			//表示查询数
1 3		//表示查询1和3 是否属于同一集合,计算s[1] & s[3] = 0110 & 0010,统计1的个数,即1和3同属于集合的个数,输出“Yes”
1 5		//s[1] & s[5] = 0110 & 0100 = 0100,统计1的个数,输出“Yes”
3 5		//s[3] & s[5] = 0010 & 0100 = 0000,统计1的个数,输出“No”
1 10	//s[1] & s[10] = 0110 & 1000 = 0000,统计1的个数,输出“No”

bitset解决

  1. 定义一个bitset数组,对每个数都用二进制表示。
  2. 根据输入数据,将原数所属集合对应的位置为1。
  3. 根据查询输入的两个数x,y,统计s[x] & s[y]运算后二进制数中1的个数,如果大于或等于1,则输出Yes,否则输出No
#include <iostream>
#include <bitset>
#include <string>
#include <memory>
using namespace std;
int main()
{
    int N,Q,num,x,y;
    scanf("%d",&N);
    unique_ptr<bitset<1010>[]> s(new bitset<1010>[N+1]);//s[x]表示元素x所属集合的二进制表示
    for (int i = 1; i <= N; ++i) {
        scanf("%d",&num);
        while(num--){
            scanf("%d",&x);
            s[x][i] = 1;
        }
    }
    scanf("%d",&Q);
    while(Q--){
        scanf("%d %d",&x,&y);
        if((s[x]&s[y]).count()){//统计与运算后二进制数中1的个数
            printf("Yes\n");
        }
        else{
            printf("No\n");
        }
    }
    return 0;
}

输入:

3
3 1 2 3
3 1 2 5
1 10
4
1 3
1 5
3 5
1 10

输出:

Yes
Yes
No
No
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羽星_s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值