用一些节点构造10台计算机, 分别实现不同的指定功能.
所有运算为浮点运算, 可以精确到小数点后90位. 与期望输出相差不超过10^-9判为正确.
节点的类型: I/O, 加法, 取相反数, 加上常数, 乘/除以2^k, 取S(x)=1/(1+e^-x), 比较 (返回-1/0/1), 取Max, 乘法. 使用最后3种会扣分. 要求节点总数尽量少.
详见: uoj #224
vfk的讲题课件很清晰, uoj群的里有修正后的版本.
以下不加区分地使用等于和约等于.
-2a-2b Problem
仔细阅读题面.
1/(1+e^(17a)) Problem
仔细阅读题面.
sgn
不扣分的节点中, 最奇怪的要数S型节点. 观察图象, 发现当x->oo, S(x)->1; x->-oo, S(x)->0. 于是, 可以认为P(x) = S(x<<150) = 0 if x<0, 0.5 if x=0, 1 if x>0. 变换后即得sgn(x).
|a|
快一年了还是不会做这个点 TAT
想着用两个Sigmoid叠加造一个偶函数出来, 但是没法还原成线性的......
|a| = max { a, -a } = a + max { 0, -2a } = a - 2 min { 0, a }
令P'(x) = P(x+eps) = 0 if x<0, 1 if x≥0.
考虑S(x+P'(x)<<500), 当x为非负数, 它等于1, 当x为负数, 它等于S(x). S'(0)=1/4. 当x->0, S(x)->x/4+0.5, 可解出x. 而x为非负数时, 用P'(x)凑一凑, 可调整为0. 于是, 我们实现了 min { 0, a }.
P'(x)<<500
中的500是可以调整的, 只要结果充分大. 此处置为152可减少一个节点, 获得满分.
二进制转十进制
直接转换即可.
十进制转二进制
从低位向高位转换需要整除, 取模这些东西, 实现起来不方便.
所以, 考虑从高位向低位转换.
我的思路是这样:
a = in()
for i = 31 downto 0
b[i] = x >= (1<<i)
x -= b[i] ? 1<<i : 0
out(b)
然后就不会捉了......没法只用190个节点.
正解是这样:
a = in()
for i = 31 downto 0
b[i] = x >= (1<<i)
x -= b[i] << i
out(b)
卡一卡常数. 比如, 把所有数左移100位, 最低一位直接输出:
n = 32
print('I')
print('< 1 100')
y = 2
now = 3
for i in range(n-1, 0, -1):
t = -2**(i+100) + 2**10
print('C {} {}'.format(y, t))
print('S {}'.format(now))
print('- {}'.format(now+1))
print('< {} {}'.format(now+2, i+100))
print('+ {} {}'.format(y, now+3))
print('O {}'.format(now+1))
y = now+4
now += 6
print('> {} 100'.format(y))
print('O {}'.format(now))
xor
先把a,b转成二进制, 按位异或, 再转回十进制.
x xor y = (x+y) mod 2 = x+y-(x+y >= 2 ? 2 : 0) (x,y in {0, 1})
用前面的P函数容易构造.
然而我在考虑这个......TAT
x xor y = min { x+y, 2-(x+y) }
除以一般常数
把1/10转成二进制小数再倍增一下?
然而满分需要节点数不超过7.
正解很有趣:
(s(x0+dx)-s(x0))/dx = s'(x0)
=> dx s'(x0) = s(x0+dx) - s(x0)
解出满足s'(x0)=0.1
的x0
即可. 注意上式仅在dx充分小时成立, 因此需要将x先缩小, 乘完之后再放大.
我写了一个牛顿切线法解方程的小程序. 实测double精度不够, 所以用了Python里的decimal.
from decimal import *
getcontext().prec = 90
e = Decimal('2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382179')
def g(x):
t = e ** -x
return t * (1 + t) ** -2
def h(x):
t = e ** -x
return (e ** (-2 * x) - t) * (1 + t) ** -3
def solve(c):
x = Decimal('2')
for i in range(1000):
x -= (g(x) - c) / h(x)
return x
print(solve(Decimal('0.1')))
考场上难以得知e
精确到小数点后90位的结果, 所以......用极限逼近一下?
也可以直接解出s'(x0)=0, 但是表达式包含ln, math模块里的精度不够.
排序
没有if
语句, 遗忘选择排序 (排序网络) 会比较方便. bubble-sort即可.
乘法取模
基本思想是用 "快速加" 代替乘法, 这样只用对 [0, 2m) 内的数取模. a mod m = 1*a mod m.
a*b的快速加可以用泰勒展开代替: s(x + ln 3) = 1/4 + 3/16 x + 3/64 x^2 - 1/256 x^3 - 5/1024 x^4 + O(x^5), 由此构造 x^2, 而 ab = ((a+b)^2 - (a-b)^2)/4.
虽然意思领会了......但还是不会用不超过2000个节点实现.
抽象出一个F函数, 算是实现一下条件表达式.
生成第一种解法的Python代码如下:
def I():
print('I')
def O(x):
print('O %d' % (x))
def A(x, y):
print('+ %d %d' % (x,y))
def C(x, c):
print('C %d %s' % (x,c))
def N(x):
print('- %d' % (x))
def L(x, k):
print('< %d %d' % (x, k))
def R(x, k):
print('> %d %d' % (x, k))
def S(x):
print('S %d' % x)
T = 1
n = 32
def F(c, x):
'''x <= 0 ? c : 0
p = S((x-1e-15)<<150)<<151
s = S((c>>150) + p)
r = ((s-0.5)<<152) - p'''
global T
C(x, '-0.000000000000001') # 0
L(T, 150) # 1
S(T+1) # 2
L(T+2, 151) # 3 (p)
N(T+3) # 4 (-p)
R(c, 150) # 5
A(T+5, T+3) # 6
S(T+6) # 7 (s)
C(T+7, '-0.5') # 8
L(T+8, 152) # 9
A(T+9, T+4) # 10 (r)
T += 11
return T-1
def convert(y):
global T
b = []
L(y, 100)
y = T
T += 1
for i in range(n-1, 0, -1):
t = -2**(i+100) + 2**10
C(y, '%d' % (t))
S(T)
N(T+1)
L(T+2, i+100)
A(y, T+3)
b.append(T + 1)
y = T + 4
T += 5
R(y, 100)
b.append(T)
T += 1
return b
def MulMod(a, b, m):
global T
R(1, 10000)
r = T
T += 1
for i in range(n-1, -1, -1):
N(r) # 0
N(a) # 1
A(T, T+1) # 2
A(T+2, m) # 3
T += 4
f = F(m, T-1)
N(f) # 0
A(a, T) # 1
N(b[i]) # 2
C(T+2, '1') # 3
T += 4
f = F(T-3, T-1)
A(r, f) # 0
r = T
L(a, 1) # 1
N(T+1) # 2
A(m, T+2) # 3
T += 4
f = F(m, T-1)
N(f) # 0
A(r+1, T) # 1
a = T+1
T += 2
return r
def main():
global T
I() # a
I() # b
I() # m
T += 3
m = 3
R(1, 10000)
C(T, '1')
T += 2
a = MulMod(T-1, convert(1), m)
O(MulMod(a, convert(2), m))
main()
3330个节点, 7分.
未完待续
97/100
yyt爷在考场上短短的几个小时就拿到了这个分......Orz
提交答案题还是挺有趣的. 人类来提供思路, 再用计算机辅助我们解决问题. APIO T2 我忘了自己还有台电脑, 手玩挂得很惨......QAQ