cf1697 D. Guess The String
作者:互联网
题意:
交互题。
猜一个小写字符串 \(s\),两种询问方式:
1 i
,回答 \(s_i\) 是什么字符2 l r
,回答 \(s_l\sim s_r\) 中的不同字符数量
$|s|\le 1000, $ 两种询问的次数限制分别为 \(26,6000\)
思路:
第一种询问不能超过 26 次,那肯定是先确定每种字符的分布(但不知道具体是什么字符),再询问具体是啥字符
首先可以用 \(n\) 次第二种询问得到每种字符第一次出现的位置。方法是询问 2 1 2, 2 1 3, 2 1 4
直到回答即不同字符数增大1,说明找到新段的起点。
岔路:对某 \(s_i\) 如何找到它右边最近的相同字符,即最小的 \(k>i, \text{ s.t. }s_i=s_k\) ?
发现对于两种询问
2 i j
和2 i+1 j-1
,若 \(j<k\) 则前者的回答比后者大2,若 \(j=k\) 则大1,若 \(j>k\) 则相同。这样可以二分,用 \(2log_2 n\) 次确定一段,确定所有段就要 \(n*2log_2n\) 次,太多了
正确的复杂度很可能形如 \(n*()\),这个 \(n\) 应该是去不掉的。那就需要把每次找相同字符的询问次数优化到 \((6000-n)/n=5\) 次左右,而 \(\lceil log_2 26\rceil\) 刚好就是 \(5\) !
设 \(s_i\) 待确定,它左边的所有位置都已确定。把 \(s_i\) 左边出现过的每种字符 \(ch\) 按最后出现位置 \(las(ch)\) 排序。然后询问 2 las(ch) i
,若回答为 \([las(ch),i-1]\) 中的种类数 \(cnt_{ch}\),就说明 \(s_i\) 就是 \([las(ch),i-1]\) 中的某种字符;若回答是 \(cnt+1\),就说明 \(s_i\) 不在其中,得往 \(las\) 值更小的字符找。
这个 $cnt_{ch} $ 就是排序后 \(las\) 数组中 \(ch\) 到末尾的元素数
注意上面搞了这么多我们都不知道每段具体是什么字符,用一下第一种询问就好了。实际写码不必先问完第2种最后才问所有第1种,怎么方便怎么写
良心 cf 交互题的询问次数限制总是贴着正确解法的复杂度
char ask1(int i) {
cout << "? 1 " << i << endl;
char c; cin >> c; return c;
}
int ask2(int l, int r) {
cout << "? 2 " << l << ' ' << r << endl;
int v; cin >> v; return v;
}
int n; string ans;
vector<pair<int,char>> las; //{最后出现位置,字符}
int findSame(int i) {
sort(all(las)); int len = las.size();
int l = 0, r = len - 1; //在las里二分
while(l < r) {
int mid = l + r + 1 >> 1;
if(ask2(las[mid].fi, i) == len-mid) l = mid;
else r = mid - 1;
}
return l;
}
void sol() {
cin >> n;
ans.pb(ask1(1)), las.pb({1,ans[0]}); //先弄一下第一个
for(int i = 2; i <= n; i++)
if(ask2(1, i) > las.size()) //全新的字符
ans.pb(ask1(i)), las.pb({i, ans.back()});
else {
int j = findSame(i); //s[j]=s[i]
ans.pb(las[j].se), las[j].fi = i;
}
cout << "! " << ans << endl;
}
标签:字符,Guess,ch,String,int,询问,cf1697,ans,las 来源: https://www.cnblogs.com/wushansinger/p/16483888.html