ARC070D

ARC070D [* easy]

\(a\) 个诚实的人和 \(b\) 个不诚实的人,你需要通过交互来确定每个人诚实与否。

  • 查询格式如下:询问 \(x\)\(y\) 是否诚实。

其中不诚实的人可以说谎(也可以说实话)。

\(n=a+b\),通过 \(2n\) 次查询确定所有人是否说谎。

或者确定不可能。

\(a,b\le 2000\)

Solution

首先不难观察出 \(a\le b\) 一定是无解的,这个时候让一个大小为 \(a\) 的子集假装自己是诚实就那么一定问不出来。

然后考虑 \(a>b\),我们的思路是在保持 \(a>b\) 的性质不变的情况下将 \(n\) 的规模缩小,同时均摊下来每两次查询确定一个人,我们可以基于这样的一个策略:

  • \(x\)\(y\) 然后让 \(y\)\(x\)

我们发现仅有三种情况:

  • 假设 \(x,y\) 都说对方是诚实的,那么两者是相同的人。
  • 假设 \(x\)\(y\) 诚实,\(y\)\(x\) 不诚实,那么 \(x\) 一定不诚实,我们通过 \(2\) 次操作确定了一个点。
  • 假设 \(x,y\) 都说对方不诚实,那么两者至少有一个不诚实,可以删除这两个点,然后我们保持着 \(a>b\) 的性质并缩小问题规模,然后我们最后找到了一个诚实的人再询问两次来确定谁诚实谁不诚实,这里一共要花费 \(4\) 次操作来确定两个人,均摊下来问每个人两次。

对于第一类人,和第二类中余下的那个不确定的人,我们递归处理。显然这样仍然保留有 \(a>b\) 的性质。

由于第二类满足 \(2\) 次确定一个人的性质,于是我们只需要证明,即使仅考虑第一类人,我们仍然能够 \(2n\) 次查询确定所有人:

考虑将相同状态的人缩成一个连通块,那么同一个连通块的人状态都是相同的,同时如果确定了一个诚实的人,那么只需要查询一次即可确定这个连通块。

所以如果仅停留于第一次递归,我们的查询次数为 \(n+n\)

如果停留于第二次递归,我们的查询次数为 \(n+\frac{n}{2}+\frac{n}{2}\)

对于更多递归,我们的查询次数为 \(n+\frac{n}{2}+...=2n\)

综上,我们可以通过均摊 \(2\) 次查询确定一个点。

唯一的问题是孤立点,我们显然的策略是直接带着孤立点进行递归,但是有反例:

  • 假设当前 \(n=7\),同时我们两两配对剩余一个,假设此时状态为 T, T, F, F 那么这样就 lose 了。

所以可以考虑直接删除孤立点,不难发现这样没影响同时保证了正确性。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define pb push_back
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int N = 4000 + 5 ; 
int A, B, n, vis[N] ; 
vector<int> f, a[N] ; 
vector<vector<int>> S ; 
string s[2] ;
vector<int> operator + (vector<int> x, vector<int> y) {
	vector<int> z ; z.clear() ; 
	for(int v : x) z.pb(v) ; for(int v : y) z.pb(v) ;
	return z ; 
}
int solve(vector<vector<int>> g) {
	int l = g.size() ; 
	if( l == 1 ) {
		for(int x : g[0]) vis[x] = 1 ; 
		return g[0][0] ; 
	}
	vector<vector<int>> zmy, uw ; 
	vector<int> d, D ; int t = 0, num = 0 ; 
	for(re int i = 0; i < l; ++ i) {
		if( !t ) d = g[i], t = g[i][0] ;  
		else {
			printf("? %d %d\n", t - 1, g[i][0] - 1 ) ; 
			cin >> s[0] ;
			printf("? %d %d\n", g[i][0] - 1, t - 1 ) ; 
			cin >> s[1] ; 
			char s0 = s[0][0], s1 = s[1][0] ;
			if( s0 == 'Y' && s1 == 'Y' ) 
				D = d + g[i], zmy.pb(D), ++ num, t = 0, d.clear() ; 
			if( s0 == 'Y' && s1 == 'N' ) d = g[i], t = g[i][0] ; 
			if( s0 == 'N' && s1 == 'N' ) 
				uw.pb(d), uw.pb(g[i]), t = 0, d.clear() ; 
		}
	}
	if( t ) {
		if( num & 1 ) t = solve(zmy), uw.pb(d) ;
		else zmy.pb(d), t = solve(zmy) ; 
	}
	else t = solve(zmy) ; 
	for(auto z : uw) {
		printf("? %d %d\n", t - 1, z[0] - 1 ) ;
		cin >> s[0] ; 
		if( s[0][0] == 'Y' ) for(int v : z) vis[v] = 1 ; 
	}
	return t ; 
}
signed main()
{
	A = gi(), B = gi() ; 
	if( A <= B ) { puts("Impossible") ; exit(0) ; }
	n = A + B ; 
	rep( i, 1, n ) a[i].pb(i), S.pb(a[i]) ; 
	solve(S) ; 
	printf("!") ; 
	rep( i, 1, n ) printf("%d", vis[i] ) ; 
	return 0 ;
}
posted @ 2020-10-27 17:49  Soulist  阅读(110)  评论(0编辑  收藏  举报