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