[ICPC2020 WF] QC QC
有 \(n\) 个人,有些人只会说真话有些人不一定,多于一半的人说真话,你需要进行不超过 12 轮询问确定哪些人一定说真话,每轮可以问每个人另一个人是不是一定说真话。
离谱题目,就要用离谱做法。这个题其实可以压到 6 轮的!
\(1\) 代表一定说真话,\(0\) 代表不一定。考虑只询问二元环,也就是每一对人互相问,这样的话如果返回 \(1,1\) 那么说明这两个人要么是 \((1,1)\) 要么是 \((0,0)\)。那么直接把这两个人的状态合并起来就行了。
接下来一个想法是如果经历若干轮之后所有的 \(1\) 都合并起来了,那么绝对众数的那个连通块就是答案。但是你发现你很难合理安排策略使得很大概率能全部合并。
考虑一些启发式的想法,第一个想法是我们询问同一个连通块的边或者同一轮询问询问同一对连通块的边一定是不优的,所以说我们只需要考虑连通块之间连边。第二个观察是真连通块趋向于很大而且能连很多边,所以说我们启发式地每次拿大点的连通块优先连。
写一个这样的算法:每次拿一个最大的连通块随机与一个没连边的其它连通块相连,发现这能过不一定话的人随机返回的交互库,但是当不一定说真话的人一直说真话时,最大的连通块并不倾向于是真的,我们需要更多的信息。
考虑我们询问一条边,如果结果是 \(1,1\) 就连边,否则说明这一对连通块间永远不可能连边了。于是你存储所有询问失效的边,然后你就可以得出所有不能问的连通块对,每次随机选取时忽略这些连通块对。这个乱搞特牛,6 轮就可以以极大概率完成操作。
添加 LOCAL
宏用于交互库本地测试。直到我写完了这题代码,zhy 才告诉我 LOJ 上有 python 交互库可以用。
#include <map>
#include <cstdio>
#include <vector>
#include <random>
#include <cassert>
#include <algorithm>
using namespace std;
mt19937 rng(random_device{}());
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int N=103;
int n,rk;
int f[N],w[N];
vector<int> vec[N];
int rt(int x){
if(f[x]==x) return x;
return f[x]=rt(f[x]);
}
int tp,sx[N],sy[N];
inline void add(int u,int v){++tp;sx[tp]=u;sy[tp]=v;}
int p[N];bool q[N],ans[N];
bool res[N],mp[N][N];
bool anti[N][N],non[N][N];
inline void sol(){
for(int i=1;i<=n;++i) p[i]=0;
for(int i=1;i<=tp;++i) p[sx[i]]=sy[i],p[sy[i]]=sx[i];
printf("test");
for(int i=1;i<=n;++i) printf(" %d",p[i]);
putchar('\n');
fflush(stdout);
#ifdef LOCAL
for(int i=1;i<=n;++i)
if(p[i]) q[i]=mp[i][p[i]];
else q[i]=1;
#else
char cc=getchar();
while(cc!='0'&&cc!='1'&&cc!='-') cc=getchar();
for(int i=1;i<=n;++i) q[i]=cc^48,cc=getchar();
#endif
for(int i=1;i<=tp;++i)
if(q[sx[i]]&&q[sy[i]]) f[rt(sx[i])]=rt(sy[i]);
else anti[sx[i]][sy[i]]=1;
}
int arr[N];
void solve(){
n=read();
for(int i=1;i<=n;++i) f[i]=i,ans[i]=0;
#ifdef LOCAL
int cnt;
do{
cnt=0;
for(int i=1;i<=n;++i) cnt+=(res[i]=rng()&1);
}while(cnt*2<=n);
for(int i=1;i<=n;++i)
if(res[i]) for(int j=1;j<=n;++j) mp[i][j]=res[j];
else for(int j=1;j<=n;++j) mp[i][j]=1;
#endif
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) anti[i][j]=0;
for(int it=0;it<6;++it){
tp=0;rk=0;
for(int i=1;i<=n;++i){
vec[i].clear();
if(f[i]==i) w[rk++]=i;
}
for(int i=1;i<=n;++i) vec[rt(i)].emplace_back(i);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
non[i][j]=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(anti[i][j]) non[rt(i)][rt(j)]=non[rt(j)][rt(i)]=1;
sort(w,w+rk,[](int x,int y){return vec[x].size()>vec[y].size();});
for(int i=0;i<rk;++i){
int x=w[i],sz=0;
if(vec[x].empty()) continue;
for(int j=i+1;j<rk;++j) if(!vec[w[j]].empty()&&!non[x][w[j]]) arr[sz++]=w[j];
for(int j=0;j<sz&&!vec[x].empty();++j){
add(vec[x].back(),vec[arr[j]].back());
vec[x].pop_back();vec[arr[j]].pop_back();
}
}
sol();
}
for(int i=1;i<=n;++i) vec[i].clear();
for(int i=1;i<=n;++i) vec[rt(i)].emplace_back(i);
for(int i=1;i<=n;++i)
if(vec[i].size()*2>n) for(int x:vec[i]) ans[x]=1;
printf("answer ");
for(int i=1;i<=n;++i)
printf("%d",ans[i]);
putchar('\n');
fflush(stdout);
#ifdef LOCAL
for(int i=1;i<=n;++i) assert(ans[i]==res[i]);
#endif
}
int main(){
int tc=read();
while(tc--) solve();
return 0;
}