AGC047E Product Simulation
题意简述
给定一个长度为 \(200,000\) 的数组 \(a\),编号从 \(0\) 开始,初始时 \(a_0=A,a_1=B\),但你不知道 \(A\) 和 \(B\)
你可以执行不超过 \(200,000\) 次操作,每次操作如下两种:
(1)+ i j k
:让 \(a_k\leftarrow a_i+a_j\)
(2)< i j k
:让 \(a_k\leftarrow a_i<a_j\)
保证 \(0\le A,B\le 10^9\),在任何时候 \(a\) 数组的元素都必须在 \([0,10^{19}]\) 内
输出一个操作序列(注意此题无输入),使得所有操作结束之后,\(a_2=A\times B\)。
Solution
首先我们需要搞出 \(1\),这个利用 \(a_{199999}<a_0\) 即可直接得到。\(a_0=0\) 另当别论。
基本操作之一:两个 \(0/1\) 值的非和与运算
非:\(\text{not }x=x<1\)
与:\(x\text{ and }y=1<x+y\)
基本操作之二:根据 \(0/1\) 状态进行赋值
这好像没有什么好做法,但我们可以实现让 \(x\) 变成 \(\begin{cases}2^z & y=1\\0 & y=0\end{cases}\)(注意这个 \(z\) 是已知的,不是数组内的元素)。
可以视为 \(x\leftarrow 2^z\times y\),故把 \(x\) 赋成 \(y\) 之后自加 \(z\) 次即可。
基本操作之三:二进制分解
即把 \(x\) 二进制分解的结果保存在 \(b_{0\dots 30}\) 内。
记录一个临时变量初始 \(y=0\),然后从高到低位考虑,把 \(b_i\) 设定为 \(\text{not }(x<y+2^i)\),然后根据 \(b_i\) 的值为 \(0\) 或 \(1\) 来决定是否要将 \(y\) 加上 \(2^i\),可以使用基本操作二。
总做法
有了三个基本操作之后,思路就比较自然了。
把 \(a_0\) 和 \(a_1\) 分别二进制分解后做个卷积(\(0/1\) 值的相乘可用 \(\text{and}\))得到答案的 \(60\) 位二进制表示之后依次加到 \(a_2\) 内即可。
不难发现,当 \(a_0=0\) 时上面的算法可以得出正确结果。操作次数为 \(O(\log^2(10^9))\)。
Code
#include <bits/stdc++.h>
const int ZERO = 199998, ONE = 199999, N = 2e5 + 5;
int tot, a[N], b[N], c[N];
char ty[N];
void push(char t, int x, int y, int z)
{
ty[++tot] = t; a[tot] = x; b[tot] = y; c[tot] = z;
}
void make(int x, int y, int c)
{
push('+', y, ZERO, x);
for (int i = 1; i <= c; i++) push('+', x, x, x);
}
void NOT(int x) {push('<', x, ONE, x);}
void AND(int x, int y, int z) {push('+', x, y, z); push('<', ONE, z, z);}
void bin(int x, int offset)
{
push('+', ZERO, ZERO, x + 3);
for (int i = 30; i >= 0; i--)
{
push('+', x + 3, 100 + i, x + 5);
push('<', x, x + 5, offset + i);
NOT(offset + i); make(x + 5, offset + i, i);
push('+', x + 3, x + 5, x + 3);
}
}
int main()
{
push('<', ZERO, 0, ONE);
push('+', ZERO, ONE, 100);
for (int i = 1; i <= 30; i++) push('+', 99 + i, 99 + i, 100 + i);
bin(0, 1000); bin(1, 2000);
for (int i = 0; i <= 30; i++) for (int j = 0; j <= 30; j++)
AND(1000 + i, 2000 + j, 2500), push('+', 2500,
3000 + i + j, 3000 + i + j);
for (int i = 0; i <= 60; i++) make(4000, 3000 + i, i),
push('+', 2, 4000, 2);
std::cout << tot << std::endl;
for (int i = 1; i <= tot; i++) printf("%c %d %d %d\n", ty[i],
a[i], b[i], c[i]);
return 0;
}