计算数字a AND s = a

时间:2015-08-02 05:01:42

标签: c++ algorithm bit-manipulation

我正在编写一个符合以下规范的程序:

  

你有一个整数列表,最初列表是空的。

     

您必须处理三种类型的Q操作:

     
      
  1. add s:将整数s添加到列表中,注意可以存在整数   列表中不止一次

  2.   
  3. del s:从列表中删除一个整数s的副本,这是有保证的   列表中至少会有一个s副本。

  4.   
  5. cnt s:计算列表中有多少个整数a,以便a   AND s = a,其中AND是按位AND运算符

  6.         

    其他限制:

         

    1≤Q≤200000

         

    0≤s< 2 ^ 16

我有两种方法但都超时,因为约束非常大。

我使用了这样的事实:当且仅当s具有a的所有设置位时,AND s = a,并且其他位可以任意分配。因此,我们可以迭代所有这些数字并将其数量增加一个。

例如,如果我们的数字为10:1010

然后数字1011,1111,1110将是这样的,当以1010结算时,他们将给出1010.所以我们将数量增加10,11,14和15比1.并且对于删除我们从他们各自删除一个计数。

有更快的方法吗?我应该使用不同的数据结构吗?

3 个答案:

答案 0 :(得分:3)

让我们考虑两种方法来解决它,两种方法很慢,然后将它们合并到一个解决方案中,这将保证在几毫秒内完成。

方法1 (慢)

分配大小为v的数组2^16。每次添加元素时,请执行以下操作:

void add(int s) {
    for (int i = 0; i < (1 << 16); ++ i) if ((s & i) == 0) {
        v[s | i] ++;
    }
}

(删除相同但减少而不​​是递增)

然后回答cnt s您只需要返回v[s]的值。要了解原因,请注意v[s]对于添加的每个数字a只增加一次a & s == a(我将离开它是为了弄清楚为什么会这样)

方法2 (慢)

分配大小为v的数组2^16。添加元素s时,只需增加v[s]。要查询计数,请执行以下操作:

int cnt(int s) {
    int ret = 0;
    for (int i = 0; i < (1 << 16); ++ i) if ((s | i) == s) {
        ret += v[s & ~i];
    }
    return ret;
}

x & ~y是一个数字,其x中设置的所有位都未在y中设置

这是一种更简单的方法,与您的工作方式非常相似,但编写方式略有不同。当我们结合这两种方法时,你会明白为什么我这样写它。

这两种方法都太慢了,因为其中一个操作是一个常量,一个是O(s),所以在最坏的情况下,当整个输入由慢操作组成时,我们花费{{1这是非常慢的。现在让我们使用中间相遇来合并这两种方法,以获得更快的解决方案。

快速方法

我们将通过以下方式合并这两种方法:O(Q * s)的工作方式与第一种方法相似,但不考虑add的每个数字a,我们只会考虑数字,仅与最低8位的a & s == a不同:

s

对于删除执行相同的操作,但不是递增元素,而是递减它们。

对于计数,我们将执行与第二种方法类似的操作,但我们将考虑到每个void add(int s) { for (int i = 0; i < (1 << 8); ++ i) if ((i & s) == 0) { v[s | i] ++; } } 已经为最低8位的所有组合累积的事实,因此我们只需要迭代所有高8位的组合:

v[a]

现在int cnt(int s) { int ret = 0; for (int i = 0; i < (1 << 8); ++ i) if ((s | (i << 8)) == s) { ret += v[s & ~(i << 8)]; } return ret; } add都在cnt中工作,所以整个方法都是O(sqrt(s)),对于你的约束应该是毫秒。

要特别注意溢出 - 你没有提供O(Q * sqrt(s))的上限,如果它太高,你可能想要用s替换int s第

答案 1 :(得分:1)

解决问题的方法之一是在每个约 sqrt(S)查询的块中打破查询列表。这是一种标准方法,通常称为 sqrt-decomposition

您必须单独存储:

  1. 数组$q = _GET['q']; A[v]出现多少次。
  2. 数组sR[v]的所有A[i]超集的i总和(即 cnt(v)的结果)。
  3. 列出当前查询块中所有更改(添加 del 操作)的v
  4. 注意:数组WA仅对完全处理的查询块中的所有更改有效。当前处理的查询块中发生的所有更改都存储在R中,尚未应用于WA

    现在我们逐块处理查询,对于我们所做的每个查询块:

    1. 对于块内的每个查询:
      • add(v):将R的增量存储到v列表中。
      • del(v):将W的减量存储到v列表中。
      • cnt(v):返回W,其中R[v] + X(W)是通过对列表X(W)中的所有更改进行微不足道的处理计算得出的总变化。
    2. W的所有更改应用到数组W,清除列表A
    3. 从数组W重新计算完整数组R
    4. 请注意添加 del 需要 O(1)时间, cnt 需要 O (| W |)= O(sqrt(S))时间。所以第1步总计需要 O(Q sqrt(S))时间。  第2步采用 O(| W |)时间,总计 O(Q)时间。

      最重要的部分是第3步。我们需要在 O(S)中实现它。鉴于存在 Q / sqrt(S)块,这将在 O(Q sqrt(S))时间内合计。

      不幸的是,重新计算数组A 只能在 O(S log S)时间中完成。这意味着 O(Q sqrt(S)log(S))时间。如果我们选择块大小 O(sqrt(S log S)),则总时间为 O(Q sqrt(S log S))。尽管如此,还是没有完美,但很有趣=)

答案 2 :(得分:0)

考虑到您在其中一条评论中描述的数据结构,您可以尝试以下算法(我在伪代码中给出它):

count-how-many-integers(integer s) {
  sum = 0 
  for i starting from s and increasing by 1 until s*2 {
       if (i AND s) == i {
            sum = sum + a[i]
       }
  }
  return sum
}

内循环中应该可以进行更复杂的优化,以减少执行测试的次数。