题目
题目背景
在公元
9021
9021
9021 年,抽水马桶统一了全世界,开创了 人类 地球的新纪元。
丑话说在前面:这是一道交互题。
题目描述(故事纯属虚构,不代表个人立场)
有一个抽水马桶(学名
s
p
l
a
s
h
y
o
u
r
a
s
s
\rm splash\;your\;ass
splashyourass,下文简称为
s
y
a
\rm sya
sya 或
s
y
\rm sy
sy)准备了八个压路机来修路(下简称为八个压路)。从空中俯瞰,
s
y
\rm sy
sy 的城市恰好是一个单位圆的
n
n
n 等分点。
为了让八个压路同时工作, s y \rm sy sy 决定修建的道路不能相交(除非是在某个城市处相交)。另外, s y \rm sy sy 只准备修建 n − 1 n-1 n−1 条路,但是要让任意两个城市之间有路径连接。
糟糕的是, s y \rm sy sy 最喜欢棕色(因为与 💩 颜色相近),最讨厌红色(因为长臂 🐒 经常来吃霸王餐)。任意两个城市之间的道路都是棕色或者红色, s y \rm sy sy 决定,道路必须全部是棕色或者红色(所谓陷之死地而后生,置之亡地而后存)。
现在,你可以询问高傲的 s y \rm sy sy 最多 2 n 2n 2n 次道路的颜色,然后告诉 T a Ta Ta 一个合法的修建道路的方案。
数据范围与提示
n
≤
1
0
3
n\le 10^3
n≤103 。提示:可以证明,总是存在合法的解。
思路
H a n d I n D e v i l \sf HandInDevil HandInDevil 太强了!他究竟是怎么想到的啊, o r z orz orz !
按照某个大佬的讲法,“黑白两个树加起来就 2 n − 2 2n-2 2n−2 条边了,一共 2 n 2n 2n 询问,基本上就是每次询问都需要有效。怎么才能做到这一点呢?考虑让连边的顺序是 距离从小到大,然后并查集判一判就过了……”
好吧,上面那个太毒瘤。我们来听听这个:总操作次数是线性,考虑 O ( 1 ) \mathcal O(1) O(1) 的代价 去掉某个点。如果随意选择一个,你会发现,它很难接入。但是, ⟨ x − 1 , x ⟩ , ⟨ x , x + 1 ⟩ \langle x-1,x\rangle,\;\langle x,x+1\rangle ⟨x−1,x⟩,⟨x,x+1⟩ 是异色的,那么无论如何都可以接入。找到这个点行吗?
一方面,不存在这样的点,就直接构造一个环即可。另一方面,找到这个点并且去掉它,剩下的递归问题中,只有 ⟨ x − 1 , x + 1 ⟩ \langle x-1,x+1\rangle ⟨x−1,x+1⟩ 需要重新询问。所以真的是 O ( 1 ) \mathcal O(1) O(1) 删除点!
加上最开始的 O ( n ) \mathcal O(n) O(n) 基础环,最多询问 2 n − 2 2n-2 2n−2 次,足以通过。
代码
题目的要求是使用 gra.hpp
中的 main()
。
#include "gra.hpp"
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
namespace Stupid{
const int MaxN = 1002;
char maze[MaxN][MaxN];
int getInfo(int a,int b){
static bool done = false;
if(!done){
done = true;
for(int i=0; i<MaxN; ++i)
for(int j=0; j<MaxN; ++j)
maze[i][j] = 2;
}
if(a > b) swap(a,b);
if(maze[a-1][b-1] != 2)
return maze[a-1][b-1];
return maze[a-1][b-1] = query(a,b);
}
int nxt[MaxN];
int solve(int n,int begin = 1){
if(n == 2){
report(begin,nxt[begin]);
return getInfo(begin,nxt[begin]);
}
for(int i=1,x=begin; i<n; ++i,x=nxt[x]){
int y = nxt[x], z = nxt[y];
if(getInfo(x,y) != getInfo(y,z)){
nxt[x] = z; // erase y
if(solve(n-1,z) == getInfo(x,y))
z = x; // link <x,y>
report(z,y); return getInfo(z,y);
}
}
for(int i=1,x=begin; i<n; ++i)
report(x,nxt[x]), x = nxt[x];
return getInfo(begin,nxt[begin]);
}
}
void tree(int n){
for(int i=1; i<=n; ++i)
Stupid::nxt[i] = i+1;
Stupid::nxt[n] = 1; // form a circle
Stupid::solve(n);
}
后记
知乎不可挡,乒乓亦难埋。本无一着错,已为人上人。——《对 H a n d I n D e v i l \sf HandInDevil HandInDevil 的判词》