目录

Codeforces 1697 D题解

D.Guess The String

题意

交互题。评测机生成一个长度为 N 的字符串 S,有两种操作:

  • 1 i,询问第 i 位字母是什么。
  • 2 l r,询问区间 [l, r] 的子字符串字符集大小。

只允许提出至多26次操作1,至多6000次操作2,确定后输出生成的字符串S。

Sample Input

5
4
u
2
g
e
s
1

Sample Ouput

? 2 1 5
? 1 2
? 2 1 2
? 1 1
? 1 3
? 1 4
? 2 4 5
! guess

思路

首先可以猜到26个操作对应26个字母,发起操作1的时刻应当是第一次或者最后一次出现某个字母时,在这个交互题中,比较显然的是:如果从左到右构造字符串,第一次出现某字母,就发起操作1。那么顺其自然的就会需要解决一个问题:怎么知道当前字母是第一次出现?

先定义f(x,y)代表[x, y]区间的子字符串的字符集大小。

解决这个问题的方式肯定是通过操作2,而操作2对应的询问得到的答案为字符集的大小。由于我们是顺序依次构造,因此当f(1,x)>f(1,x - 1)的字符集大小大时,就意味着x这个位置出现的字母,从未出现在1~x中,因此就可以发起操作1去询问这个s[x]。通过这样的方式,我们就可以先确定每个字母出现的第一个位置。

以上的方式已经决定了,接下来要确定其他位置的字母,只能够通过操作2的询问。

假设最极端的情况,s[1] ~ s[26]都是不一样的字母,那么我们在最开头就花费了所有的操作1次数,同时也意味着s[27]一定是重复字母。 实际剩下的问题也是这样:当f(1,y) == f(y - 1) (y > 1)时,如何确定s[y]。

实际上从题目所给出的限制可以得出:s[y]的询问次数,只能最多6次(6*1000)。此时只能通过操作2进行询问,操作2的反馈仅有字符集大小,而我们在此时是知道s[1] ~ s[y - 1]的所有字符。

字符集大小最大为26,而确定s[y]的操作次数上限为6次,比较容易想到的就是二分。问题中隐含的单调性则是:从左到右,字符串越长,字符集大小单调递增。因此我们可以二分字符集大小,来确定,s[y]处在二分区间的左半还是右半。我们可以计算得出的是:f(mid, y - 1),通过询问得出f(mid, y),如果f(mid, y - 1) == f(mid, y),就意味着s[y]出现在了mid的左半边。

那么要符合这个等式得出的结论,需要先计算出f(mid, y - 1),字符如果出现在[mid, y - 1]区间内,则最后一次出现的位置一定在[mid, y - 1]中(废话)。因此记录下每个字符出现的最后一个位置,然后计算[mid, y - 1]中多少个字符最后一次出现,就知道了f(mid, y -1)。

代码

void solveD(){
	int n;
	scanf("%d", &n);
	char s[n + 5];

	s[n + 1] = '\0';
	printf("? 1 1\n");
	fflush(stdout);
	scanf(" %c", &s[1]);
	PIC lastPos[26];
	int tot = 1;
	lastPos[tot] = PIC(1, s[1]);

	for(int i = 2; i <= n; ++i){
		printf("? 2 1 %d\n", i);
		fflush(stdout);
		int sz;
		scanf(" %d", &sz);
		if(sz != tot) {
			printf("? 1 %d\n", i);
			fflush(stdout);
			scanf(" %c", &s[i]);
			lastPos[++tot] = PIC(i, s[i]);
			continue;
		}
		sort(lastPos + 1, lastPos + tot + 1);
		int l = 1, r = tot;
		int ans = 0;
		while(l <= r){
			int mid = l + r >> 1;
			printf("? 2 %d %d\n", lastPos[mid].first, i);
			fflush(stdout);
			scanf(" %d", &sz);
			if(sz == tot - mid + 1) ans = mid, l = mid + 1;
			else r = mid - 1;
		}
		s[i] = lastPos[ans].second;
		lastPos[ans].first = i;
	}
	printf("! %s\n", s + 1);
	fflush(stdout);
}