我正在编写一个符合以下规范的程序:
你有一个整数列表,最初列表是空的。
您必须处理三种类型的Q操作:
add s:将整数s添加到列表中,注意可以存在整数 列表中不止一次
del s:从列表中删除一个整数s的副本,这是有保证的 列表中至少会有一个s副本。
- 醇>
cnt s:计算列表中有多少个整数a,以便a AND s = a,其中AND是按位AND运算符
其他限制:
1≤Q≤200000
0≤s< 2 ^ 16
我有两种方法但都超时,因为约束非常大。
我使用了这样的事实:当且仅当s具有a的所有设置位时,AND s = a,并且其他位可以任意分配。因此,我们可以迭代所有这些数字并将其数量增加一个。
例如,如果我们的数字为10:1010
然后数字1011,1111,1110将是这样的,当以1010结算时,他们将给出1010.所以我们将数量增加10,11,14和15比1.并且对于删除我们从他们各自删除一个计数。
有更快的方法吗?我应该使用不同的数据结构吗?
答案 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 。
您必须单独存储:
$q = _GET['q'];
:A[v]
出现多少次。s
:R[v]
的所有A[i]
超集的i
总和(即 cnt(v)的结果)。v
。 注意:数组W
和A
仅对完全处理的查询块中的所有更改有效。当前处理的查询块中发生的所有更改都存储在R
中,尚未应用于W
和A
。
现在我们逐块处理查询,对于我们所做的每个查询块:
R
的增量存储到v
列表中。W
的减量存储到v
列表中。W
,其中R[v] + X(W)
是通过对列表X(W)
中的所有更改进行微不足道的处理计算得出的总变化。W
的所有更改应用到数组W
,清除列表A
。W
重新计算完整数组R
。请注意添加和 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
}
内循环中应该可以进行更复杂的优化,以减少执行测试的次数。