CF1634D - Finding Zero(构造性算法 + 数学规律 + 交互题 / 铁牌级)
1634D - Finding Zero(源地址自⇔CF1634D)
tag
⇔构造性算法、⇔数学规律、⇔交互题、⇔铁牌级(*2000)
题意
有一个长度为 \(N\) 的序列,其中包含一个 \(0\) ,你需要通过询问得到信息,找到这个 \(0\) 所在的位置。规定操作如下:
- 你至多可以询问 \(2*N-2\) 次;
- 对于每次询问,你需要给出三个数字 \(i,j,k\) ,然后你会得到 \(max(a_i,a_j,a_k)−min(a_i,a_j,a_k)\) 的值。
思路
官方思路(数学规律)
对于任意四个位置,我们都可以通过四次查询找到其中肯定不是 \(0\) 的两个位置:不妨假设有 \(A,B,C,D\) 四个位置,对应的数字为 \(a,b,c,d\) ,满足 \(a=0,b<c<d\) ,那么查询结果如下:
\[\left\{\begin{matrix}
\bar{a} & = & max(b,c,d)-min(b,c,d) & =d-b \\
\bar{b} & = & max(a,c,d)-min(a,c,d) & =d \\
\bar{c} & = & max(a,b,d)-min(a,b,d) & =d \\
\bar{d} & = & max(a,b,c)-min(a,b,c) & =c \\
\end{matrix}\right.\]
我们可以发现,查询结果最大的两个数字( \(b\) 和 \(c\) )必定不是 \(0\) 。相似的,对于另外四个非零的数字,这一规律同样适用。
至此,我们得到规律:每次查询四个位置,丢掉结果最大的两个,然后引入两个未查询的位置,继续这一循环,直到查询完所有的位置。特别的,对于 \(N\) 为奇数的情况,我们会遇到未查询的位置不满两个的情况,这时候我们只需要引入一个此前丢掉的位置即可继续这一循环。
我们可以通过证明复杂度来确保该算法无误:当 \(N\) 为偶数时,至多 \(\frac{n-2}{2} * 4=2*n-4\) 次询问,当 \(N\) 为奇数时,至多 \(\frac{n-3}{2} * 4+4=2*n-2\) 次询问,均满足题意。
AC代码(数学规律)
点击查看代码
//====================
int n;
tuple<int, int, int, int> Ready;
pair<int, int> Pre;
//====================
void check() {
auto [aa, bb, cc, dd] = Ready;
int a, b, c, d;
cout << "? " << bb << " " << cc << " " << dd << endl; cout.flush();
cin >> a;
cout << "? " << aa << " " << cc << " " << dd << endl; cout.flush();
cin >> b;
cout << "? " << aa << " " << bb << " " << dd << endl; cout.flush();
cin >> c;
cout << "? " << aa << " " << bb << " " << cc << endl; cout.flush();
cin >> d;
if(max(a, max(b, max(c, d))) == a) {
if(max(b, max(c, d)) == b) Pre = {cc, dd};
if(max(b, max(c, d)) == c) Pre = {bb, dd};
if(max(b, max(c, d)) == d) Pre = {bb, cc};
}else if(max(a, max(b, max(c, d))) == b) {
if(max(a, max(c, d)) == a) Pre = {cc, dd};
if(max(a, max(c, d)) == c) Pre = {aa, dd};
if(max(a, max(c, d)) == d) Pre = {aa, cc};
}else if(max(a, max(b, max(c, d))) == c) {
if(max(a, max(b, d)) == a) Pre = {bb, dd};
if(max(a, max(b, d)) == b) Pre = {aa, dd};
if(max(a, max(b, d)) == d) Pre = {aa, bb};
}else if(max(a, max(b, max(c, d))) == d) {
if(max(a, max(b, c)) == a) Pre = {bb, cc};
if(max(a, max(b, c)) == b) Pre = {aa, cc};
if(max(a, max(b, c)) == c) Pre = {aa, bb};
}
}
void Solve() {
cin >> n;
Pre = {1, 2};
FOR2(i, 3, n - 1) {
Ready = {Pre.fi, Pre.se, i, i + 1}; //备选项为上一轮留下的两个位置+新加入的两个位置
check();
}
if(n % 2 == 1) { //引入一个此前已经丢掉的位置
vector<int> add;
if(Pre.fi != 1 && Pre.se != 1) add.pb(1);
if(Pre.fi != 2 && Pre.se != 2) add.pb(2);
if(Pre.fi != 3 && Pre.se != 3) add.pb(3);
Ready = {Pre.fi, Pre.se, n, add[0]};
check();
}
cout << "! " << Pre.fi << " " << Pre.se << endl; cout.flush();
}
错误次数
文 / WIDA
2022.02.22 成文
首发于WIDA个人博客,仅供学习讨论
更新日记:
2022.02.22 成文