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

漏斗计算

题面描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原题实际上是瓶子国的故事的子任务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(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;
}

谢谢!!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值