题解:SP10077 ROUTING - Routing
ZnPdCo
·
·
题解
一个 n-Benes 网络由交换器组成。一个交换器有两个输入端 X, Y 和两个输出端 X', Y'。如果交换器处在关闭状态,那么 X 端的信号就直接被发送到 X',Y 端的信号发送到 Y';否则,如果交换器打开,那么 X 端的信号就发送到 Y',Y 端的信号就发送到 X'。
而 Benes 网络是递归定义的。1-Benes 网络就是一个单纯的交换器,而 n-Benes 网络的第一排是 2^{n-1} 个交换器,也就有 2^n 个输入端和输出端,从左到右用 n 位二进制数表示。第一排的输出端 k,与第二段的输入端 k \gg 1 相连,其中 \gg 表示循环右移,即最后一位移位之后变成第一位。在第一排后面放置两个 (n-1)-Benes 网络。在最后一排再放置 2^{n-1} 个交换器,倒数第二排的输出端 k 连向最后一排的输入端 k \ll 1,其中 \ll 表示循环左移,亦即前两排之间输入输出端口变换的逆变换。一个 3-Benes 网络如下图所示。
通过恰当地设置 Benes 网络中的交换器,可以实现整个网络输入端与输出端的变换。上图的右侧实现了输出端 0 \sim 7 分别与输入端 3, 7, 4, 0, 2, 6, 1, 5 相接的 3-Benes 网络。
你的任务是,对于给定的 n-Benes 网络与变换,求一个满足要求的交换器配置。
输入文件包含多个测试数据。每个测试数据包含两行,第一行是一个整数 n,表示你要处理的是 n-Benes 网络;第二行包含 0 到 2^n - 1 的一个排列,排列的第 k 个数 a_k 表示输出端 k 需要与输入端 a_k 相连。
输入文件以 0 结尾。
对于每个测试数据,打印 2n + 1 行 2^{n - 1} 位二进制数,表示配置好的 n-Benes 网络。二进制位 1 表示对应位置的交换器是打开的,0 表示交换器是关闭的。
保证有解,如有多解,打印字典序最小的。
在每个答案之后打印一个空行。
---
为了方便,我们定义各排的交换器和端口都从 $0$ 开始标号。
由于这个网络本身就是递归定义的,于是很容易想到要递归求解。
我们只需要搞定第一排和最后一排交换器,就得到了给第二排交换器的输入和倒数第二排交换器的预想输出。然后递归这个过程就行了。
当 $n=1$ 时,直接处理就好了。
我们观察图片,不难发现:从第一排交换器的输出端输出的信号,如果是偶数号码的端口,就进入了左边的 Benes 网络,否则进入右边的 Benes 网络。而左边网络输出的信号都流进了最后一排交换器的偶数号码的端口,右边的信号都流进了奇数号码的端口。
那么如果第一排输入端第 $A$ 个端口和最后一排输出端第 $B$ 个端口相连,如果 $A$ 与 $B$ 的奇偶性相同,那么 $A$ 所在交换器(显然是第一排的第 $\lfloor A/2\rfloor$ 个交换器)和 $B$ 所在交换器(显然是最后一排的第 $\lfloor B/2\rfloor$ 个交换器)的状态也要相同,反之则不同。
似乎我们可以使用种类并查集来实现这个东西!原题需要字典序最小的解,那么就让第一排靠前的交换器尽量为 $0$ 就行了。
递归求解即可。
---
一些正确性的证明:
1. 让第一排靠前的交换器尽量为 $0$ 就可以使得字典序尽量小。事实上是因为确定了第一排的取值就可以确定最后一排的取值;
2. 如果原问题有解。递归得到的子问题是否一定是有解的?
事实上我们有一个更强的结论:原问题的任何实例均有解。
从上面那个并查集的角度来看,反证她,我们只需要使得某个交换器交换和不交换的状态在同一个并查集内就好了。
我们考虑连一条红边 $(A,B)$ 表示交换器 $A$ 与交换器 $B$ 状态不同,反之相同则连蓝边。
那么如果一个环内由奇数个红边,那么可以从交换器 $A$ 走一圈这个环达到与最开始相反状态的交换器 $A$,无解。
因为每个点度数为 $2$,所以原图由若干个环组成,同时注意到每个环由偶数条蓝边和红边组成。
所以没有奇数个红边,原猜测成立。
---
没有真正地去实现一个递归,代码会更可爱一些:
```cpp
#include <bits/stdc++.h>
#define N 50000
using namespace std;
// ak,bk:终点、起点的k号信号的位置
int n, m, mm, a[N], b[N], tmp[N], ans[30][N], fa[N];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
int merge(int x, int y) { fa[find(x)] = find(y); return 0; }
int calc(int x, int k) {
x = n - x;
int A = k >> x << x, B = k ^ A;
B = (B >> 1) | ((B & 1) << (x - 1));
return A | B;
}
bool solve() {
scanf("%d", &n);
if(n == 0) return 0;
m = 1 << n, mm = m >> 1;
memset(ans, 0, sizeof(ans));
for(int i = 0; i < m; i++) scanf("%d", &tmp[i]), a[tmp[i]] = i, b[i] = i;
for(int i = 0, j; i < n - 1; i++) {
j = 2 * n - 2 - i, iota(fa, fa + m * 4 + 1, 0);
for(int k = 0; k < m; k++)
merge((b[k] >> 1), (2 + ((b[k] ^ a[k]) & 1)) * m + (a[k] >> 1)),
merge(m + (b[k] >> 1), (3 - ((b[k] ^ a[k]) & 1)) * m + (a[k] >> 1));
for(int k = 0; k < mm; k++)
ans[i][k] = (find(k) == find(m * 4)) || merge(m + k, m * 4);
for(int k = 0; k < mm; k++)
ans[j][k] = find(2 * m + k) == find(m * 4);
for(int k = 0; k < m; k++) tmp[b[k]] = k;
for(int k = 0; k < mm; k++) if(ans[i][k]) swap(tmp[k * 2], tmp[k * 2 + 1]);
for(int k = 0; k < m; k++) b[tmp[k]] = calc(i, k);
for(int k = 0; k < m; k++) tmp[a[k]] = k;
for(int k = 0; k < mm; k++) if(ans[j][k]) swap(tmp[k * 2], tmp[k * 2 + 1]);
for(int k = 0; k < m; k++) a[tmp[k]] = calc(i, k);
}
for(int i = 0; i < m; i++) if(a[i] != b[i]) ans[n - 1][a[i] >> 1] = 1;
for(int i = 0; i < 2 * n - 1 || printf("\n") - 1; i++)
for(int k = 0; k < mm || printf("\n") - 1; k++) printf("%d", ans[i][k]);
return 1;
}
int main() { while(solve()); }
```