漏斗计算
题面描述
原题实际上是瓶子国的故事的子任务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(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define debug(x) cerr<<#x<<"="<<x<<'\n'
typedef long long LL;
typedef unsigned long long uLL;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int mo=998244353;
const int inv2=499122177;
const int jzm=2333;
const int n1=50;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<LL,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
void print(_T x){putchar('\n');while(x>9){putchar((x%10)|'0');x/=10;}putchar(x|'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int tot,pl1[35],pl2[35],bita[35],bitb[35];
signed main(){
//freopen("liver.in","r",stdin);
//freopen("liver.out","w",stdout);
puts("I");int a=++tot;
puts("I");int b=++tot;
puts("C 536870912");tot++;
puts("C 536870912");tot++;
for(int i=0;i<30;i++)
printf("C %d\n",(1<<i)),pl1[i]=++tot,
printf("C %d\n",(1<<i)-1),pl2[i]=++tot;
for(int i=16;i>=0;i--){
printf("E %d\n",pl1[i]);
printf("E %d\n",pl2[i]);
printf("F %d\n",a);
printf("T %d %d\n",a,pl1[i]);
printf("T %d %d\n",pl1[i],pl2[i]);
printf("M %d\n",pl1[i]);bita[i]=++tot;
for(int j=0;j<i;j++){
puts("E 4");
printf("F %d\n",bita[i]);
printf("T %d 4\n",bita[i]);
printf("F %d\n",bita[i]);
printf("T %d 4\n",bita[i]);
puts("M 4");bita[i]=++tot;
}
printf("F %d\n",a);
printf("T %d %d\n",a,bita[i]);
printf("M %d\n",a);a=++tot;
}
for(int i=16;i>=0;i--){
printf("E %d\n",pl1[i]);
printf("E %d\n",pl2[i]);
printf("F %d\n",b);
printf("T %d %d\n",b,pl1[i]);
printf("T %d %d\n",pl1[i],pl2[i]);
printf("M %d\n",pl1[i]);bitb[i]=++tot;
for(int j=0;j<i;j++){
puts("E 4");
printf("F %d\n",bitb[i]);
printf("T %d 4\n",bitb[i]);
printf("F %d\n",bitb[i]);
printf("T %d 4\n",bitb[i]);
puts("M 4");bitb[i]=++tot;
}
printf("F %d\n",b);
printf("T %d %d\n",b,bitb[i]);
printf("M %d\n",b);b=++tot;
}
for(int i=0;i<17;i++){
printf("E %d\n",bita[i]);
for(int j=i;j<29;j++){
puts("E 4");
printf("F %d\n",bita[i]);
printf("T %d 4\n",bita[i]);
printf("F %d\n",bita[i]);
printf("T %d 4\n",bita[i]);
puts("M 4");bita[i]=++tot;
}
for(int j=0;j<min(18-i,17);j++){
if(i){
puts("E 4");
printf("F %d\n",bitb[j]);
printf("T %d 4\n",bitb[j]);
printf("F %d\n",bitb[j]);
printf("T %d 4\n",bitb[j]);
puts("M 4");bitb[j]=++tot;
}
printf("F %d\n",bitb[j]);
printf("T %d %d\n",bitb[j],bita[i]);
}
printf("T %d 3\n",bita[i]);
}
puts("M 3");int now=++tot;
for(int i=23;i>17;i--){
printf("E %d\n",pl1[i]);
printf("E %d\n",pl2[i]);
printf("F %d\n",now);
printf("T %d %d\n",now,pl1[i]);
printf("T %d %d\n",pl1[i],pl2[i]);
printf("M %d\n",pl1[i]);int x=++tot;
for(int j=0;j<i;j++){
puts("E 4");
printf("F %d\n",x);
printf("T %d 4\n",x);
printf("F %d\n",x);
printf("T %d 4\n",x);
puts("M 4");x=++tot;
}
printf("F %d\n",now);
printf("T %d %d\n",now,x);
printf("M %d\n",now);now=++tot;
}
printf("F %d\n",now);
printf("P %d\n",now);
return 0;
}