题目描述
有一天,你实验室的老板给你布置的这样一个实验。
首先他拿出了两个长度为 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
数据范围
对于 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
枚举某个k位二进制数的子集不需要$O(2^k)$
看如下代码
for(int j=a[i];j>0;j=((j-1)&a[i]))
这样便枚举了$a_i$的所有子集,复杂度为其个数
然后再用一个pos数组标记某个子集出现的最晚位置就好了
时间复杂度:$O(3^k),其中k=logn$
算法合理性证明:https://www.jianshu.com/p/cfe562dcf2f6
时间复杂度证明:https://www.cnblogs.com/MyNameIsPc/p/8876855.html
#include<bits/stdc++.h> using namespace std; const int N=1e5+5; int n; int a[N],b[N]; int pos[N]; int main() { freopen("test.in","r",stdin); freopen("test.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d%d",&a[i],&b[i]); for(int i=1;i<=n;++i) { int ans=0; for(int j=a[i];j>0;j=((j-1)&a[i])) { if(pos[j]<i-b[i]) ++ans; pos[j]=i; } printf("%d\n",ans); } return 0; }