题目链接
题意:让猜出一个长度为
n
n
n的01串,每次询问
[
l
,
r
]
[l,r]
[l,r],随机翻转
[
1
,
r
]
或
[
l
,
n
]
[1,r]或[l,n]
[1,r]或[l,n],返回翻转后序列中1的个数。翻转就是把每一位上的数取反。在10000次询问内结束。
n
≤
300
n\le 300
n≤300。
题解
随机翻转让人很难过,不妨来分析一下如何才能准确判断翻转的是左边还是右边。
不难发现,翻转长度为奇数的区间会导致1的变化量为奇数;否则变化量为偶数。也就是说,我们如果让
[
1
,
r
]
[1,r]
[1,r]和
[
l
,
n
]
[l,n]
[l,n]长度的奇偶性不同,就可以判断出翻转的是哪边的了。
其次,由于是随机翻转,很难达到我们想要的效果。我们想要的无非就是翻转固定的区间,这可以通过多次翻转实现。由于是随机的,我们可以证明期望恰好翻转左右中的一个区间,期望翻转次数是3次。
于是思路就比较明了了,分
n
n
n的奇偶性讨论:
若
n
n
n为偶数,则任意
i
i
i和
n
−
i
+
1
n-i+1
n−i+1的奇偶性不同,于是我们可以通过翻转前缀来得到原串的前缀和。
设前缀中原来有
x
x
x个1,长度为
i
i
i,翻转前和翻转后1的总数分别为
t
,
t
′
t,t'
t,t′,则
t
′
=
t
−
x
+
i
−
x
t'=t-x+i-x
t′=t−x+i−x,易解出
x
x
x。
若 n n n为奇数,则任意 i i i和 n − i n-i n−i的奇偶性不同,我们每次操作 [ i , i + 1 ] [i,i+1] [i,i+1],也可以得到翻转前缀的效果。但是第一个数和第二个数无法求出。于是我们考虑翻转 [ 2 , n ] [2,n] [2,n]这个后缀,于是可以求出 [ 2 , n ] [2,n] [2,n]中有多少个1,问题就完美解决了。
注意每次翻完后要翻回来,以及特判 n = 1 n=1 n=1的情况。期望操作次数为 6 n 6n 6n。
#include <bits/stdc++.h>
using namespace std;
int n, cnt;
int ask(int l, int r) {
printf("? %d %d\n", l, r);
fflush(stdout);
int t; scanf("%d", &t);
return t;
}
int turn(int l, int r, int t, int er = 1, int el = 0) {//turn [1,r]
int gl = 0, gr = 0;
while (true) {
int t1 = ask(l, r);
if (((t - t1) & 1) == (r & 1)) gr ^= 1;
else gl ^= 1;
if (gr == er && gl == el) return t1;
t = t1;
}
}
char str[305];
int main() {
scanf("%d%d", &n, &cnt);
if (n == 1) printf("! %c\n", '0' + cnt);
else if (n & 1) {
int lst = 0;
for (int i = 1; i < n; i++) {
int t = turn(i, i + 1, cnt);
int a = (cnt + i - t + 1) >> 1;
if (i > 1) str[i + 1] = a - lst + '0';
lst = a;
turn(i, i + 1, t);
}
int t = turn(2, n, cnt, 0, 1);
int s = (cnt - t + n - 1) >> 1;
for (int i = 3; i <= n; i++) s -= str[i] - '0';
str[2] = s + '0';
for (int i = 2; i <= n; i++) cnt -= str[i] - '0';
str[1] = cnt + '0';
printf("! %s\n", str + 1);
} else {
int lst = 0;
for (int i = 1; i <= n; i++) {
int t = turn(i, i, cnt);
int a = (cnt + i - t) >> 1;
str[i] = '0' + a - lst;
lst = a;
turn(i, i, t);
}
printf("! %s\n", str + 1);
}
return 0;
}