【IOI 2018】Combo 组合动作(模拟,小技巧)
IOI的签到题感觉比NOI的签到题要简单啊,至少NOI同步赛我没有签到成功……
其实这个题还是挺妙妙的,如果能够从题目出发,利用好限制,应该是可以想到的做法的。
接下来开始讲解具体的做法:
题目中有一个重要的限制就是答案序列首字母不会出现多次,这意味着当我们知道首字母后,接下来序列中的候选字符就只剩下$3$个了,以及我们可以在一个询问中用首字母来分割多个你想要知道的字符串。
很显然我们可以用二分找到首字母,这将花费$2$次询问机会。方便起见,我们把首字母定为$a$,剩下的$3$个字符分别为$b,c,d$。
剩下还有$n-1$个位置,通常的思路就是逐位确定。
假设我们已经找到了答案的前$i$位组成的字符串是$ans$,我们考虑怎么确定第$i +1$位是什么。
朴素的方法可以试$2$次,每次就是询问$ans+b$和$ans+c$,就能知道下一个字符是什么了。
但我们发现,题中限制每次询问的长度不超过$4n$,显然朴素的方法有点浪费。我们考虑能不能一次询问多个字符串使得把$2$次询问合并在一起呢?
这个思路是可行的,我们可以在一次询问中放入$4$个字符串:$ans + b + b + ans + b + c + ans + b + d + ans + c$,由于首字母只出现一次,所以我们询问的四个串的答案是分开算取$max$的。我们发现如果下一位是$b$,那么应该为返回$i + 2$,如果下一位是$c$,那会返回$i + 1$,否则就会返回$i$,我们用恰好$4n$的长度在一次询问里区别了三个字符。
有一个小问题要注意,如果$i = n - 1$时,按照上述做法会超出$4n$的长度限制,故结尾处需要用$2$次询问。
总的询问次数就是$2 + n - 2 + 2$,共$n+2$次,刚好达到题目的限制。
给出需要实现的函数:
#include "combo.h" #include <algorithm> using namespace std; string guess_sequence(int n) { string p = "", ans = ""; char a, b, c, d; if (press("AB") > 0) { c = 'X', d = 'Y'; press("A")? (a = 'A', b = 'B') : (a = 'B', b = 'A'); } else { c = 'A', d = 'B'; press("X")? (a = 'X', b = 'Y') : (a = 'Y', b = 'X'); } ans = a; if (n == 1) return ans; for (int i = 1; i < n - 1; ++i) { p = ans + b + b + ans + b + c + ans + b + d + ans + c; int bk = press(p); if (bk == i + 0) ans += d; if (bk == i + 1) ans += c; if (bk == i + 2) ans += b; } if (press(ans + b) == n) ans += b; else if (press(ans + c) == n) ans += c; else ans += d; return ans; }