做实验 解题报告(二进制枚举子集)

题目描述

有一天,你实验室的老板给你布置的这样一个实验。
首先他拿出了两个长度为 n 的数列 a 和 b,其中每个 a i 以二进制表示一个集
合。例如数字 5 = (101) [2] 表示集合 {1, 3}。第 i 次实验会准备一个小盒子,里面装
着集合 a i 所有非空子集的纸条。老板要求你从中摸出一张纸条,如果满足你摸出的
纸条是 a i 的子集而不是 a i−b i ,a i−b i +1 ,...,a i−1 任意一个的子集,那么你就要被阿掉;
反之,你就逃过一劫。
令你和老板都没有想到的是,你竟然每次都逃过一劫。在庆幸之余,为了知道
这件事发生的概率,你想要算出每次实验有多少纸条能使你被阿掉

输入格式

第一行一个数字 n。
接下来 n 行,每行两个整数,分别表示 a i 和 b i 。

输出格式

n 行,每行一个数字,表示第 i 次实验能使你被啊掉的纸条数。

样例输入 1

3
7 0
15 1
3 1

样例输出 1

7
8
0
4

数据范围

对于 30% 的数据,n, a i , b i ≤ 100
对于 70% 的数据,n, a i , b i ≤ 60000
对于 100% 的数据,n, a i , b i ≤ 10 5
保证所有的 a i 不重复,b i < i

题解

看上去是可以在线做的
关键是枚举每个数的子集 暴力求二进制下每一位是否为1的话是无法表示一个子集的(反正我不会 大多数人也不会 会也不必要在这个题上)
for(int i=a;i;i=(i-1)&a)就可以枚举了
证明:对于第一个真子集(a-1)&a,这个-1把二进制表示下的a的数值为1的最后一位变成了0,而这一位后面的0都变成了1,再&一下原来的a,后面的1又变回了0,而变为0的那一位就没有变回去
所以这一操作就直接搞掉了最后一个1(看不懂就对着我刚刚说的模拟一遍,eg:10110)
同时可以用一个f数组记录一下该子集的最后出现在哪一个大集合里

代码

#include <cstdio>
#include <cmath>
#define ll long long
#define R register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
inline int read(){
    int x=0,f=1;char c=getchar();
    while (c>'9'||c<'0') {if (c=='-') f=-1;c=getchar();}
    while (c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
const int maxn=1e5+5;
int n,a,b,ans,f[maxn];
void init(){
    n=read();
}
void doit(){
    for (R int i(1);i<=n;++i){
        a=read(),b=read();
        ans=0;
        for (R int j(a);j;j=(j-1)&a){//枚举子集
            if (f[j]<i-b) ++ans;//判断是否在该数的前b个数的子集里
            f[j]=i;
        }
        printf("%d\n",ans);
    }
}
signed main(){
//  file("test");
    init();
    doit();
    return 0;
}

转载于:https://www.cnblogs.com/cancers/p/11304042.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值