P8135 [ICPC2020 WF] QC QC
P8135 [ICPC2020 WF] QC QC
两种人,
1 或0 。1 只说真话,0 对于询问相同的人的状态的回答相同。进行不超过12 轮询问,每轮询问形如向第i 个人问第p_i 个人的状态。需保证p_i 互不相同且i \neq p_i 。已知1 的人数严格大于0 的人数,试确定每个人的状态。
高妙构造题。
首先,我们只能从回答的结果反推出每个人的状态,且一定是让两个人相互询问。若不相互询问,我们难以确定任何一个人的状态,因此导致获得的信息完全无用。
考虑两个
根据上述结论,我们考虑将所有人划分为若干 等价类,每个等价类内部所有人的状态均相同。一开始,每个人都是一个等价类。称为 一型等价类。
接下来,将所有一型等价类两两配对并互相询问。若询问结果不是
每次配对的过程中,可能有单独的一型等价类剩余。丢到一边,称为 三型等价类。
- 性质 1:任何时候任何一型等价类和三型等价类的大小均为
2 的幂。 - 性质 2:不会出现大小相同的三型等价类。
- 性质 3:一型等价类和三型等价类的
1 的个数严格大于0 的个数。这是因为二型等价类中0 不少于1 (关键性质)。
考虑配对的最终结果。显然,若一型等价类个数
因此,最终不剩余任何一型等价类。根据上述三条性质,容易发现必然存在三型等价类,且最大的的三型等价类必然为
现在我们用较少的步数确定了至少一个
除非运气特别差,
#include <bits/stdc++.h>
using namespace std;
#define vint vector <int>
#define pb push_back
string query(vint p) {
string res;
cout << "test ";
for(int it : p) cout << it << " ";
cout << endl, cin >> res;
return res;
}
const int N = 100 + 5;
int n;
vector <int> id[N], t1, t2, t3, fix;
void merge(vint &x, vint y) {for(int it : y) x.push_back(it);}
void solve() {
cin >> n, t1.clear(), t2.clear(), t3.clear(), fix.clear();
for(int i = 1; i <= n; i++) id[i].clear(), id[i].pb(i), t1.pb(i);
while(t1.size()) {
vector <int> p(n), nt1;
for(int i = 0; i + 1 < t1.size(); i += 2) p[t1[i] - 1] = t1[i + 1], p[t1[i + 1] - 1] = t1[i];
if(t1.size() & 1) t3.pb(t1.back());
string res = query(p);
for(int i = 0; i + 1 < t1.size(); i += 2) {
int x = t1[i], y = t1[i + 1];
if(res[x - 1] == '1' && res[y - 1] == '1') merge(id[x], id[y]), nt1.pb(x);
else t2.pb(x), t2.pb(y);
} t1 = nt1;
}
int bk = t3.back(), cur = 0;
merge(fix, id[bk]), t3.pop_back();
vector <int> p(n);
for(int it : t3) p[id[bk][cur++] - 1] = it;
string res = query(p); cur = 0;
for(int it : t3) if(res[id[bk][cur++] - 1] == '1') merge(fix, id[it]);
while(t2.size()) {
int lim = min(fix.size(), t2.size());
vector <int> p(n), nfix = fix;
for(int i = 0; i < lim; i++) p[fix[i] - 1] = t2.back(), t2.pop_back();
string res = query(p);
for(int i = 0; i < lim; i++) if(res[fix[i] - 1] == '1') merge(nfix, id[p[fix[i] - 1]]);
fix = nfix;
}
string ans;
for(int i = 0; i < n; i++) ans += '0';
for(int it : fix) ans[it - 1] = '1';
cout << "answer " << ans << endl;
}
int main() {
int T; cin >> T;
while(T--) solve();
}
优化:注意到当且仅当最后一次合并时,将偶数个大小为
一个乱搞方法(来源于 LOJ 第一篇提交):考虑每次将两个等价类合并。对于所有不同的等价类对
启示:构造题当中