AtCoder Regular Contest 070: F - HonestOrUnkind
solution
2B回ぐらい使って長さB+1の含意のpathを作って正直者ひとりを確定し、残りはすべてこの人に聞く。時間計算量O(A+B)。
A≤Bの場合は明らかにImpossible
。不親切な人全員が一貫して嘘を付けば正直者と区別できない。
正直者がひとりでも判明すれば、後はN−1回その人に聞けば終わりである。
つまりN+1回以内で正直者をひとり見付ければよい。
人iが正直であることをϕ(i)と書くとする。 人iに人jが親切かどうか尋ねると、(ϕ(i)→ϕ(j))⊕(ϕ(i)→¬ϕ(j))のどちらかが得られる。 これを使ってϕ(i0)→ϕ(i1)→⋯→ϕ(iB)という互いに異なる人による長さB+1の鎖が得られれば、不親切は高々B人なので正直者が含まれておりまた真であることが推移することからϕ(iB)は真である。 i0=0から始めて構築していく。ϕ(i)→¬ϕ(j)が得られた場合が問題であるが、この場合i,jをまとめて鎖から除いてしまう。 ϕ(i)∧ϕ(j)は偽なので必ずBがdecrementされるのでこれは高々B回発生する。 発生により目標の鎖の長さは1縮めてよいことと、(iを鎖に入れるためのクエリが無駄になるとしても)B<Aなので2B<Nより問題にはならない。
implementation
#include <cstdio>
#include <vector>
#include <stack>
#include <cassert>
#define repeat(i,n) for (int i = 0; (i) < int(n); ++(i))
using namespace std;
int main() {
int a, b; scanf("%d%d", &a, &b);
if (a <= b) {
printf("Impossible\n");
} else {
auto query = [](int i, int j) {
printf("? %d %d\n", i, j);
fflush(stdout);
char c; scanf(" %c", &c);
switch (c) {
case 'Y': return true;
case 'N': return false;
default: assert (false);
}
};
// find an honest man
stack<int> chain;
repeat (i, a + b) {
if (chain.empty()) {
chain.push(i);
} else {
if (query(chain.top(), i)) {
chain.push(i);
} else {
chain.pop();
}
}
}
// ask him
assert (not chain.empty());
int honest = chain.top();
vector<bool> result(a + b);
repeat (i, a + b) {
result[i] = query(honest, i);
}
// output
printf("! ");
repeat (i, a + b) printf("%d", int(result[i]));
printf("\n");
}
return 0;
}