提供一种不同于官方题解、需要的操作次数比官方题解多(官方题解大概是 (2 imes 16),我这大概是 (3 imes 16)),但能通过此题的做法。
首先我们考虑一个暴力,我们设一个阈值 (B=2^k),然后我们考虑预处理出 (1sim B) 中所有数,即,先一遍 p1
,然后每次 dup
一遍,然后调用 add
指令给栈顶上的数加 (1)(显然此时 (1) 在栈中),如此操作 (B-1) 次即可让 (1sim B) 中所有数都在栈中。然后再新开一个元素表示答案,然后我们就每 (k) 个元素一块,从高位开始,每次乘以 (2^k)(由于 (2^k) 此时已经在栈中,直接调用 mul
指令即可),然后取出这 (k) 位表示的数,调用 add
指令累加到答案中即可,不难发现这样操作次数为 (2(B+dfrac{64}{k})) 级别的,取 (k=4) 即 (B=16) 时最优。
但这样需要 (4 imes 16) 次操作,无法卡过本题 (50) 次的限制,考虑优化,注意到本题还有一个 sub
命令我们没有调用,因此我们考虑这样一件事情,我们第一部分只预处理出 (1sim 8) 附加上 (16),这样我们第二部分时,假设我们目前处理的这 (k) 位值为 (v),那么如果 (vle 7),显然 (v) 此时已经在栈中,我们就直接用 add
命令将 (v) 加到栈顶元素上去即可,但如果 (vge 8) 就不太好直接处理了。一个很直观的想法是,我们直接加 (16),然后减掉该减的部分,但这样还是会卡成 (4 imes 16),样例 (N=2^{64}-1=18446744073709551615) 都过不了,不过注意到在上一步中我们已经乘了个 (16),因此我们考虑在上一轮操作中“预测未来”,即,如果下一轮表示的数 (ge 8) 那么我们就让这轮中最后四位表示的数变成 (v+1),否则直接调到 (v) 即可,这样就省去了那个 (+16) 的操作了。这样操作次数就是 (3 imes 16) 级别的了。
还有一些地方需要卡卡,否则可能会出现 (51) 次操作的悲催情况。
u1s1 这题操作次数卡得是真的紧,下面这份代码中操作次数的上限就是 (50),在 (n=2^{63}-1) 处取到:
void solve(){
u64 x;scanf("%llu",&x);
printf("p1
");
for(int i=2;i<=8;i++){
printf("dup
");
printf("add %d
",i-1);
} printf("dup
add 1
");
int need=(x>>60&15)+((x>>56&15)>=8);
if(!need) printf("p1
sub 9
");
else if(need==1) printf("p1
");
else if(1<need&&need<=9){
printf("p1
add %d
",11-need);
} else if(need>9){
printf("dup
");
if(need!=16) printf("sub %d
",10-(16-need));
} printf("mul 1
");
for(int i=14;~i;i--){
int v1=(x>>(i<<2)&15),v2=(!i)?0:(x>>(i-1<<2)&15);
if(v1<8&&v2<8){
if(v1) printf("add %d
",10-v1);
} else if(v1<8&&v2>=8){
printf("add %d
",9-v1);
} else if(v1>=8&&v2<8){
printf("sub %d
",10-(16-v1));
} else {
if(v1^15) printf("sub %d
",11-(16-v1));
} if(i) printf("mul 1
");
} printf("end
");
}
int main(){
int qu;scanf("%d",&qu);
while(qu--) solve();
return 0;
}
/*
7
15
16
16843009
1061109567
1152921504606846975
1152921504606846976
2305843009213693951
*/