P1737 旷野大计算
CuteSheep128 · · 题解
Update on 2024-2-23/17:10 修改了部分笔误
Update on 2024-2-28/15:58 Latex修复
题目链接:P1737 [NOI2016] 旷野大计算
题目大意:用取反,位移,等操作,以及非常玄学的 为什么不用冯诺依曼的存储程序思想,差评!
其中
还有三个非常好用但要扣分的节点:比较节点、MAX 节点、乘法节点。
Task 1
输入:
输入限制:
输出:
满分行数:
难度:送分
显然我们要对样例优化,我们可以可以将两个数的取反和乘
代码如下:
I
I
+ 1 2
< 3 1
- 4
O 5
Task 2
输入:
输入限制:
输出:
满分行数:
难度:送分
注意到
I
< 1 4
+ 1 2
- 3
S 4
O 5
Task 3
输入:
输入限制:
输出:
满分行数:
难度:大众
我们连 if,else 都没有,肯定不能用正常方式构造了。
首先我们可以用比较节点,与
如何造
0 ?可以用
a-a 然后比较大小。
别急,我们阅读阅读题面:
每个跳蚤的计算能力都是有限的,他们仅可以精确到十进制小数点后
90 位,超过的部分将会被四舍五入。同理,上述表格中的操作数c 的小数部分也不能超过90 位。另外,左移节点和右移节点中的操作数k 必须是非负整数,且不能超过10^4 。
但是:
你构造的计算机的输出中的某个值与预期的输出值相差超过
10^{-9} ,则认为你的输出不正确,得0 分。
为啥精确了
当然是因为跳蚤国王只看前
而且为什么大部分测试点,都没有体现精确度这个东西,而且从表面上看
要是出题人真的想没事找事的话为啥不是
为啥不是
为啥不是
这时我们需要关注
输入限制:
输出:
满分行数:
难度:较难
看得出来从这个点开始,做法就越来越复杂了。
我们知道
那我们肯定要用和
可问题来了,我们
那怎么办呢?
是时候运用一下导数了。
什么是导数?
导数是对一个函数某一处的增长率,我们可以简单地认为这是函数的增长速度,从几何意义上来讲它的含义是函数图像上某一点的切线的斜率。
一般来讲我们会把
f(x) 的导数记为{f}'(x)
{f}'(x)=\lim_{\Delta x \to 0}\frac{f(x+\Delta x)-f(x)}{\Delta x}
我们可以算出
这时我们可以轻易得写出一条
声明:为表示方便,下称这条切线为
如果我们的
而我们如果得知某一处
我们就可以从
这样我们就可以在将
但我们还需要分段。
运用
解释:如果
x>0 那么T(x)\times2^p 的贡献就为极大值,得到S 的极限。
如果
x<0 那么T(x)\times2^p 的贡献为0 ,剩下的\frac{x}{2^k} 由于非常接近0 而非常接近f(x) 。
都让
接着我们减去
可是那个
那我们可以减去
我们不妨令
我们把它乘上
好耶!
还有个小问题:
如果
我们可以对
可这样就
我们把
我们可以让
答案就是:
代码如下(
I
C 1 0.0000000001
< 2 41
S 3
< 4 180
> 1 178
+ 6 5
S 7
C 8 -0.5
< 9 181
- 10
+ 11 5
+ 12 1
O 13
Task 5
输入:
输入限制:
输出:把
满分行数:
难度:送分
又是一道送分点,每一位都向都左移一下,加一起就行。只要别干移
代码如下:
for(int i=1;i<=32;++i) printf("I\n");
for(int i=1;i<=31;++i) printf("< %d %d\n",i,32-i);
printf("+ 63 32\n");
for(int i=33;i<=63;++i) printf("+ %d %d\n",i,i+31);
printf("O 94");
Task 6
输入:
输入限制:
输出:输出
满分行数:
难度:大众
又搞
我们如何判断呢?
我们可以用和手动计算差不多的方法来算,但还得倒序,很难受,下面说一种从高位到低位取的方法。
我们先取最高位:
先将
当然我们发现左移__int128。
当然加上
对于最后一位,我们只需右移
细细一算,取一位需要
但我们刚刚的方法却多了一行。
我们需要优化循环外的一行,我们可以把加上
对于每次减去第一个减去
因为当为
代码如下:
printf("I\n");
printf("< 1 41\n");
for(int i=31,row=3;i>=1;i--,row+=6){
printf("C %d -",row-1);
cout<<str[i]<<endl;
printf("S %d\n",row);
printf("O %d\n",row+1);
printf("- %d\n",row+1);
printf("< %d %d\n",row+3,i+41);
printf("+ %d %d\n",row-1,row+4);
}
printf("> 188 41\n");
printf("O 189\n");
这里的 str[i] 就不公开了,因为
Task 7
输入:
输入限制:
输出:
满分行数:
难度:较难
首先我们知道,我们得对
我们先看异或的运算规则:
我们的一个位,只有
我们可以先对
你家异或长这样啊?!
别急我们可以对我们可以用
我们惊奇的发现我们把上面两个式子相减不就得到了吗?
但我们又发现这样写要
我们可以只用上面的一个式子:
就只要 (出题人人还怪好的嘞!)
代码太长了我给出异或的部分:
for(int i=?,j=1;j<=32;j++,i+=?)printf("+ %d %d\n",i,i+?);
for(int i=?,j=1;j<=32;j++,i++)printf("C %d -1.5\n",i);
for(int i=?,j=1;j<=32;j++,i++)printf("< %d 41\n",i);
for(int i=?,j=1;j<=32;i++,j++)printf("S %d\n",i);
for(int i=?,j=1;j<=32;j++,i++)printf("< %d 1\n",i);
for(int i=?,j=1;j<=32;j++,i++)printf("- %d\n",i);
for(int i=?,j=1;j<=32;j++,i++)printf("+ %d %d\n",i,i-?);
这里的参数需和你的代码匹配。
Task 8
输入:
输入限制:
输出:
满分行数:
难度:奇门淫技
可以用乘法节点,得
我们知道:
只要加足够多就可以接近答案了,但
这时我们要用
还记得那条切线
我们也可以找到一个数
不妨令
解得
我们用
所以
算出来之后,我们可以写出一条在此处的切线:
我们知道当
那么我们不妨求出
那么我们减去
答案就是
用计算器计算出 2.0634370688955605467272811726201。
再用 checker 算出 0.887298334620741688517926539978236773937633025540819832675154107295416657242528255923059519。
代码如下:
I
> 1 178
C 2 2.0634370688955605467272811726201
S 3
C 4 -0.887298334620741688517926539978236773937633025540819832675154107295416657242528255923059519
< 5 178
O 6
Task 9
输入:
输入限制:
输出:输出
满分行数:
难度:最难
首先这些计算节点太单一了,比汇编还要单一,我们也只能用冒泡排序等简单的排序了。
我们如何对两个数交换呢?
w=a+b
p=w-a
q=w-p
假如我们不要交换呢?
w=a+b
p=w-b
q=w-p
那假如我说 若
w=a+b
p=w-min(a,b)=a+b-(min(a-b,0)+b)=a-min(a-b,0)
q=w-p
为啥要写成这样的样子呢?
我们是不是在
这不刚好吗?
用一个数组来记录每个变量的位置,然后倒序输出即可。
代码如下:
int pos[20]={},row=16;
for(int i=1;i<=16;i++)pos[i]=i;
for(int i=1;i<=16;i++)printf("I\n");
for(int i=1;i<=16;i++){
for(int j=1;j+i<=16;j++){
printf("+ %d %d\n",pos[j],pos[j+1]);
printf("- %d\n",pos[j+1]);
printf("+ %d %d\n",pos[j],row+2);
printf("C %d 0.00000000001\n",row+3);
printf("< %d 41\n",row+4);
printf("S %d\n",row+5);
printf("< %d 179\n",row+6);
printf("> %d 178\n",row+3);
printf("+ %d %d\n",row+7,row+8);
printf("S %d\n",row+9);//10
printf("C %d -0.5\n",row+10);
printf("< %d 180\n",row+11);
printf("- %d\n",row+7);
printf("+ %d %d\n",row+12,row+13);
printf("- %d\n",row+14);
printf("+ %d %d\n",pos[j],row+15);
pos[j]=row+16;
printf("- %d\n",row+16);
printf("+ %d %d\n",row+1,row+17);
pos[j+1]=row+18;
row+=18;
}
}
for(int i=16;i>=1;i--)printf("O %d\n",pos[i]);
Task 10
输入:
输入限制:
输出:
满分行数:
难度:较难
先讲乘法:
显然有了前面的铺垫我们可以用二进制拆分的形式来计算。
我们可以将
现在问题变成构造函数:
这和
我们可以用和
一样令
现在就解决啦!
接着是取模:
我们可以用做减法我们不妨将
代码如下:
printf("I\nI\nI\n");
printf("< 2 41\n");
for(int i=31,j=5;i>=1;i--,j+=14){
if(i==31)printf("C %d -",j-1);
else printf("C %d -",j-10);
cout<<str[i]<<endl;
printf("S %d\n",j);
printf("- %d\n",j+1);
printf("< %d %d\n",j+2,i+41);
if(i==31)printf("+ %d %d\n",j-1,j+3);
else printf("+ %d %d\n",j-10,j+3);
printf("C %d -1\n",j+1);
printf("< %d 151\n",j+5);
printf("> 1 %d\n",150-i);
printf("+ %d %d\n",j+7,j+6);
printf("S %d\n",j+8);
printf("C %d -0.5\n",j+9);
printf("< %d 152\n",j+10);
printf("- %d\n",j+6);
printf("+ %d %d\n",j+12,j+11);
}
printf("> 429 41\n");
printf("C 439 -1\n");
printf("< 440 151\n");
printf("> 1 150\n");
printf("+ 442 441\n");
printf("S 443\n");
printf("C 444 -0.5\n");
printf("< 445 152\n");
printf("- 441\n");
printf("+ 446 447\n");
printf("+ 18 448\n");
for(int i=1,j=32,k=449;i<=30;i++,j+=14,k+=1)printf("+ %d %d\n",j,k);
printf("C 479 0.00000000001\n");
for(int i=64,j=481;i>=0;i--,j+=15){
printf("< 3 %d\n",i);
printf("- %d\n",j);
printf("+ %d %d\n",j+1,j-1);
printf("< %d 41\n",j+2);
printf("S %d\n",j+3);
printf("C %d -1\n",j+4);
printf("< %d 151\n",j+5);
printf("> %d 150\n",j+1);
printf("+ %d %d\n",j+7,j+6);
printf("S %d\n",j+8);
printf("C %d -0.5\n",j+9);
printf("< %d 152\n",j+10);
printf("- %d\n",j+6);//12
printf("+ %d %d\n",j+12,j+11);
printf("+ %d %d\n",j+13,j-1);
}
printf("O 1455\n");
总结
-
-
细心!细心! 细心!
-
脑洞!脑洞!脑洞!