2016 华科校赛 B. And

// 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[i] 的值就是按位与是 i 的子序列的数目。

考查这个问题的输入和输出,我们建立这样一个分治模型:work(int cnt[LEN], int ans[LEN])。其中 cnt[] 是输入,ans[] 是输出。cnt[i] 表示 a[] 中有多少个 i;ans[i] 表示有多少子序列按位与结果是 i。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]。

代码

#include <stdio.h>   
#include <string.h>   
    
#include <algorithm>   
#include <vector>   
    
const int MAXL = (1 << 20);   
const int MOD = (int) 1e9 + 7;   
    
int add(int a, int b)   
{   
  int c = a + b;   
  if (c >= MOD)   
    c -= MOD;   
  return c;   
}   
    
int sub(int a, int b)   
{   
  int c = a - b;   
  if (c < 0)   
    c += MOD;   
  return c;   
}   
    
int mul(int a, int b)   
{ return (long long) a * b % MOD; }   
    
typedef std::vector<int> array;   
    
int pow2[MAXL];   
    
void work(const array& cnt, array& ans)   
{   
  if (cnt.size() == 1) {   
    ans[0] = sub(pow2[cnt[0]], 1);   
    return;   
  }   
  int d = (cnt.size() >> 1);   
  array cnt1(d);   
  array ans1(d);   
  for (int i = 0; i < d; ++i)   
    cnt1[i] = cnt[i + d];   
  work(cnt1, ans1);   
  array cnt2(d);   
  array ans2(d);   
  for (int i = 0; i < d; ++i)   
    cnt2[i] = add(cnt[i], cnt[i + d]);   
  work(cnt2, ans2);   
  for (int i = 0; i < d; ++i) {   
    ans[i] = sub(ans2[i], ans1[i]);   
    ans[i + d] = ans1[i];   
  }   
}   
    
int a[MAXL];   
    
int main(int argc, const char* argv[])   
{   
  pow2[0] = 1;   
  for (int i = 1; i < MAXL; ++i) {   
    pow2[i] = add(pow2[i - 1], pow2[i - 1]);   
  }   
  int n, m;   
  array cnt(MAXL);   
  array ans(MAXL);   
  for (; scanf("%d", &n) == 1; ) {   
    std::fill(cnt.begin(), cnt.end(), 0);   
    for (int i = 0; i < n; ++i) {   
      scanf("%d", a+i);   
      cnt[a[i]]++;   
    }   
    work(cnt, ans);   
    scanf("%d", &m);   
    for (int i = 0; i < m; ++i) {   
      scanf("%d", a+i);   
      printf("%d ", ans[a[i]]);   
    }   
    printf("\n");   
  }   
  return 0;   
}

转载于:https://www.cnblogs.com/gu-castle/p/5545194.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值