题目传送门
分析:
读题目所给的距离的定义能够明白
设两字符串\(A,B\)的距离为\(dis(A,B)\),最长公共子序列为\(f(A,B)\),那么:
\(dis(A,B)+f(A,B)=max(|A|,|B|)\)
这样我们能够用他的距离函数进行如下操作:
1、查询某一种字符的出现次数,可以构造全为某种字符\(c\)的长度为128的字符串\(C\),\(c\)的出现次数即为\(128-dis(S,C)\)
2、查询一个长度不大于\(S\)的串\(C\)是否为\(S\)的子序列,只需判断\(f(S,C)==|C|\),即\(|S|-dis(S,C)==|C|\)
\(Q\)大概是\(O(LlogL)\)级别的,考虑分治算法
先把每一种字符的出现次数求出来,之后启发式合并两种字符集\({A},{B}\),合并直接暴力判断插入某一种字符是否合法即可,一次合并查询次数为\(|A|+|B|\)
由于启发式合并,询问次数是\(O(LlogL)\)级别的
交互题必被开除人籍系列
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<string>
#define maxn 1000005
#define INF 0x3f3f3f3f
using namespace std;
inline int getint()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
int N=128,C=62,n;
vector<string>S;
inline bool cmp(string x,string y)
{return x.size()<y.size();}
inline char id(int x)
{
if(x<26)return x+65;
if(x<52)return x+71;
return x-4;
}
inline int query(string x)
{
cout<<"? "<<x<<endl;
return getint();
}
inline bool check(string x)
{return query(x)+x.size()==n;}
int main()
{
for(int i=0;i<C;i++)
{
string x(N,id(i));
int tmp=N-query(x);
n+=tmp;if(tmp)S.push_back(string(tmp,id(i)));
}
while(S.size()>1)
{
sort(S.begin(),S.end(),cmp);
string a=S[0],b=S[1],res;
S.erase(S.begin()),S.erase(S.begin());
while(a.size()&&b.size())
if(check(res+a[0]+b))res+=a[0],a.erase(a.begin());
else res+=b[0],b.erase(b.begin());
res+=a,res+=b;
S.push_back(res);
}
cout<<"! "<<S[0]<<endl;
}