// 2016 华科校赛 B. And
http://acm.hust.edu.cn/problem/show/1672
题目描述
给出 a[1], ..., a[n]。查询 t[1], ... t[m],问有多少 a[] 的子序列的按位与是 t[]。
输入
多组测试,EOF 结束。
n
a[]
m
t[]
1 ≤ n ≤ 1e6
1 ≤ m ≤ (1 << 20)
1 ≤ a[i] ≤ (1 << 20)
1 ≤ t[i] ≤ (1 << 20)
输出
输出一行,每组查询输出一个数,每个数后面一个空格。
来源
sheep
思路
先膜拜一会儿……【三分钟之后】好了开始讲题解。
用分治的思想解决。目的是对于给定的a[],处理出一个数组ans[]。ans[x]的值就是按位与是x的子序列的数目。
考查这个问题的输入和输出,我们建立这样一个分治模型:void work(int cnt[LEN], int ans[LEN]);
。其中cnt[]是输入,ans[]是输出。cnt[x]表示a[]中有多少个x;ans[x]表示有多少子序列按位与结果是x。LEN是当前值区间的长度,也就是说当前的wrok解决的问题是[0, LEN)范围内的结果。
对一个取值区间[0, 2L)分析解,最后答案就是[0, 1<<20)的解。
我们发现较大的一半[L, 2L)是容易解决的,因为这里面的数最高位是1,要按位与结果是这里面的数,选择的数也一定得在这个范围内才行(选择了一个[0, L)内的数之后,按位与最高位就是0了)。所以这个部分可以递归使用work解决,具体方法是 work(cnt1[], ans1[])
,cnt1[i]是cnt[i + L];ans1[i]将是ans[i + L]。
但是较小的一半是不容易解决的,对i ∈ [0, L),可以选择[0, L)中的数,也可以选择[L, 2L)中的,只要最高位的1被与掉了就可以。既然如此我们不妨考虑整体,不直接计算ans[i],而计算ans[i] + ans[i + L]。这个值的意义就是,无视最高位是多少,按位与的结果的剩下的部分是i的子序列数目。无视最高位,只要把cnt[i]和cnt[i + L]也就是一样的数。调用的方法是work(cnt2[], ans2[])
,这里的cnt2[i] = cnt[i] + cnt[i + L],ans2[i]将是ans[i] + ans[i + L]。
分完这两部分,就可以解决ans[]的计算了,对于i ∈ [L, 2L),ans[i] = ans1[i - L];对于i ∈ [0, L),ans[i] = ans2[i] - ans1[i]。