[海军国际项目办公室]漏斗计算

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 x1的容器。
我们将 a a a加入 x x x中,显然,如果 a ⩾ x a\\geqslant x ax,那么 x x x是满的,否则 x x x中只有 x − 1 x-1 x1是满的。
我们将 x x x倒到 x − 1 x-1 x1的容器中去,此时,只有在 a ⩾ x a\\geqslant x ax时,我们的 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 a2i,我们就对 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 17i上的值扩大两倍,加到 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+...+b17i)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以上是关于[海军国际项目办公室]漏斗计算的主要内容,如果未能解决你的问题,请参考以下文章

[海军国际项目办公室]打拳

[海军国际项目办公室]羽未

[海军国际项目办公室]石子游戏

[海军国际项目办公室]游戏

[海军国际项目办公室]假人

[海军国际项目办公室]快递