[海军国际项目办公室]漏斗计算
Posted StaroForgin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[海军国际项目办公室]漏斗计算相关的知识,希望对你有一定的参考价值。
漏斗计算
题面描述
原题实际上是瓶子国的故事的子任务9,由校监改编。
但一个子任务就已经很恶心了,总共有10个子任务
题解
首先,我们考虑如何实现下面操作。
判断一个未知大小的容器
a
a
a是否大于等于给定的数
x
x
x,如果大于等于
x
x
x,得到一个大小为
1
1
1的容器,否则得到一个大小为
0
0
0的容器。
显然,上面的操作可以这样实现。
我们将容器
a
a
a填满,造出一个大小为
x
x
x和
x
−
1
x-1
x−1的容器。
我们将
a
a
a加入
x
x
x中,显然,如果
a
⩾
x
a\\geqslant x
a⩾x,那么
x
x
x是满的,否则
x
x
x中只有
x
−
1
x-1
x−1是满的。
我们将
x
x
x倒到
x
−
1
x-1
x−1的容器中去,此时,只有在
a
⩾
x
a\\geqslant x
a⩾x时,我们的
x
x
x中才会有
1
1
1的大小。
我们按
x
x
x现在的球数建一个容器,就可以得到我们想要的了。
有了这个操作,我们就可以实现我们的乘法操作了。
我们对
a
a
a询问数
1
,
2
,
.
.
.
,
∞
1,2,...,\\infty
1,2,...,∞就可以得到
a
a
a个大小为
1
1
1的容器,以及
∞
\\infty
∞个大小为
0
0
0的容器。
我们将它们都扩大无限倍,显然,大小为
0
0
0的容器容量不会变,但大小为
1
1
1的容器会变成
i
n
f
t
y
infty
infty。
我们往所有容器中都尝试加一个
b
b
b,那么最后会有
a
a
a个
b
b
b,我们把它们加在一起就是
a
b
ab
ab了。
不过我们完全没有必要一个一个地减小
a
a
a。
我们可以像倍增一样,从大到小枚举
a
a
a的每个二进制位。
如果
a
⩾
2
i
a\\geqslant 2^i
a⩾2i,我们就对
a
a
a减去
2
i
2^i
2i,再将我们的容器扩到无限大,加入
2
i
2^{i}
2i个
b
b
b。
对
a
a
a减去
2
i
2^{i}
2i的过程我们需要将我们得到的那个
0
/
1
0/1
0/1容器扩大到
2
i
2^i
2i倍,将
a
a
a填满,往
2
i
2^i
2i的容器中倒球,倒了后根据
a
a
a的剩余球数新建容器,置换掉
a
a
a即可。
而
0
/
1
0/1
0/1容器的扩倍与
b
b
b容器的扩倍是一样的,我们像快速幂一样,不断对容器乘
2
2
2,也就是将该容器填满球往别的容器中倒两次,再赋值回来即可。
整个过程我们必然再每个
a
a
a的二进制位为
1
1
1的位置,都加上了
2
i
2^i
2i个
b
b
b,总共加起来就是
a
b
ab
ab了。
这样的话我们也很容易想到我们的取模操作了。
由于我们的模数是个
2
2
2的幂数,
262144
=
2
18
262144=2^{18}
262144=218,我们可以枚举那些大于
2
17
2^{17}
217的二进制位,将它们按上面的方法都减去,就可以保留下来前
18
18
18个二进制位,得到答案了。
非常简单的取模技巧,这种取模方法是
O
(
log
2
n
)
O\\left(\\log^2\\,n\\right)
O(log2n)。
但我们观察到一点,
注意一个漏斗组的容量有限,不超过 1 0 9 10^{9} 109。
而我们的
a
×
b
a\\times b
a×b最大可能达到
1
0
10
10^{10}
1010,这意味着我们不能全部乘完后再一次性取模。
但如果我们在加的过程中不断取模的话,我们的取模次数就会达到
log
n
\\log\\,n
logn的级别,那么总操作次数就是
O
(
log
3
n
)
O\\left(\\log^3n\\right)
O(log3n),加上我们巨大的常数,显然是不行的。
得考虑其它办法,减小取模的次数
但这时小香猪跳出来,哞哞(沐目)几声,大意是,我们可以将 a a a和 b b b都按位拆分,将 a a a与 b b b乘起来小于 2 18 2^{18} 218的两位乘起来加入答案,显然,这样我们的到的和较小的,只需要取模一次。
我们记
a
i
a_{i}
ai表示
a
a
a在第
i
i
i位上的取值,同理,
b
i
b_{i}
bi表示
b
b
b在第
i
i
i位的取值,它们的取值要么是
2
i
2^i
2i,要么是
0
0
0。
我们枚举从
0
0
0到
17
17
17在
a
a
a的二进制位,将它们都扩大无限倍,显然,只有
a
a
a二进制位上为
1
1
1的部分是无限大的。
如果我们当前枚举的是第
i
i
i为,我们就将
b
b
b在
0
0
0到
17
−
i
17-i
17−i上的值扩大两倍,加到
a
i
a_{i}
ai中去。
当
i
i
i变到
k
k
k时,我们就已经将要用的部分的
b
b
b扩大
2
k
2^{k}
2k倍了。
显然,要用的
b
b
b的集合是单调不增的。
那么,我们每个
a
i
a_{i}
ai,存的就是
2
i
(
b
0
+
.
.
.
+
b
17
−
i
)
o
r
0
2^i(b_{0}+...+b_{17-i})\\,or\\,0
2i(b0+...+b17−i)or0。
我们将所有的
a
a
a加起来,就可以得到
a
b
ab
ab的值了。
显然,这样我们就可以将我们的总操作次数压缩到
O
(
log
2
n
)
O\\left(\\log^2n\\right)
O(log2n)了,最后的值最大也很小,最后取一次模就可以了。
时间复杂度 O ( log 2 n ) O\\left(\\log^2\\,n\\right) O(log2n),总操作次数 O ( log 2 n ) O\\left(\\log^2n\\right) O(log2n),但常数较大,大概有 6 × 1 0 3 6\\times 10^3 6×103次操作。
源码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000005
#define lowbit以上是关于[海军国际项目办公室]漏斗计算的主要内容,如果未能解决你的问题,请参考以下文章