传送门:
https://codeforces.com/contest/1697/problem/D
题意:
给定一个长为n的字符串(n<=1000),你可以用最多26次操作一和最多6000次操作二来猜出这个字符串。
有两种操作:
操作一:选择一个位置p,得到第p个字符。
操作二:选择一段区间l,r,得到[l,r]这段区间有多少不同的字母。
分析:看到26和6000,容易想到log(26)=6,考虑二分。假设当前位置在i,且i之前的字母全部确定,容易想到二分m,每次查看操作二[m,i]和[m,i-1]的答案是否相同来确定当前字母,如果这个字母没有在之前出现过,那么用操作一。至此,需要用操作二n*2*log(n)次,我们需要一些优化。发现二分到最后确定的字母一定是这个字母最后一次出现,所以我们记一个pos数组,用于记录每个字母最后一次出现的位置,每次二分也只二分这些位置,需要的操作二次数来到n*2*6次。这时候其实已经不用两次使用操作二了,直接二分m是前面出现的第几个字母即可,至此,需要的操作二次数来到n*6次。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
char ans[1005];
int pos[30];//记录每个字母最后出现的位置
int cnt;
char ask1(int p)
{
cout<<"? 1 "<<p<<endl;
char c;
cin>>c;
return c;
}
int ask2(int l,int r)
{
cout<<"? 2 "<<l<<" "<<r<<endl;
int x;
cin>>x;
return x;
}
signed main()
{
int n;
cin>>n;
ans[1]=ask1(1);
pos[++cnt]=1;
for(int i=2;i<=n;i++)
{
sort(pos+1,pos+cnt+1);
int l=1,r=cnt,p=-1;
while(l<=r)//查找当前ans[i]在前面出现的位置p
{
int m=l+r>>1;
if(ask2(pos[m],i)==cnt-m+1)
{
l=m+1;
p=m;
}
else r=m-1;
}
if(p==-1)
{
char c=ask1(i);
ans[i]=c;
pos[++cnt]=i;
}
else
{
ans[i]=ans[pos[p]];
pos[p]=i;
}
}
cout<<"! ";
for(int i=1;i<=n;i++)
{
cout<<ans[i];
}
cout<<endl;
}