大意:
给出一个串
s
s
s,每次删除其中的一种元素,将剩下的元素加到串
t
t
t 上,直至将所有元素删完。
现给出串
t
t
t,输出串
s
s
s 和删除顺序。
思考:
先求出删除顺序:
最后剩下的肯定是最后才删除的,那倒数第二个剩下的就是倒数第二个删除的,依此类推。。
所以从后往前枚举,先出现的为后删除的。
将第一次出现的元素依次存下来,遍历完之后翻转就行了。
如何求原串s呢?
类似一种哈希的思想:
最后一定要根据一个串来求出删除后的串的,但是如果时直接枚举的话,复杂度就太大了。
于是我们先根据一些限制筛选掉很大一部分,然后就可以暴力判断是否是答案了。
将每一次删除元素后加进来的串看成一组,那整个串 t t t 就分成了若干组:第一组为原串,第二组为删除一种元素后的串,第三组为删除两个元素后的串…
最后删除的元素是存活了所有组的,倒数第二个删除的元素存活的x-1组…
所以我们可以先按照当前枚举的前缀,推算出总共有多少元素。
看其是否与所给汇合串的长度相同:
- 如果小于所给串长度,说明还没枚举到,继续枚举;
- 如果和所给串长度相同,那当前枚举的前缀可能就是答案,暴力求出总串,判断是否相同;
- 如果大于所给串长度,那就无解了。
因为后面继续枚举也是以当前串为前缀,当前串推算出的总元素都大于总串了,那后面的肯定都不满足。
实现:
从前往后遍历每个位置,将从开头到该位置(前缀)看作答案。
预处理出每个元素出现的组数,遍历的时候加上当前元素出现的总组数,得到的数就是当前答案推算出的总串长度。
然后比较其与所给总串的长度大小:
- 大于所给串长度,输出-1,无解。
- 等于所给串长度,暴力求总串,判断是否与所给总串相同。如果是,那这就是答案。
Code:
#define mem(a,b) memset(a,b,sizeof a)
#include<iostream>
#include<cstring>
using namespace std;
const int N=500010;
int n,m,A[30];
string a,s,ans;
bool f[30];
bool check(string ans) //还原删除后的串
{
string t=ans;
for(int k=0;k<s.size();k++)
{
int len=t.size();
for(int i=0;i<ans.size();i++)
{
if(ans[i]=='*') continue;
if(ans[i]!=s[k]) t+=ans[i];
else ans[i]='*';
}
}
if(t==a) return 1;
return 0;
}
int main(){
int T;cin>>T;
getline(cin,a);
while(T--)
{
getline(cin,a);
s=""; //找到删除顺序s
mem(f,0);
for(int i=a.size()-1;i>=0;i--)
{
if(!f[a[i]-'a']) s+=a[i],f[a[i]-'a']=1;
}
reverse(s.begin(),s.end());
//A[i]表示i元素存活的回合数
for(int i=0;i<s.size();i++) A[s[i]-'a']=i+1;
int sum=0,flag=0;
ans="";
for(int i=0;i<a.size();i++) //判断以此前缀为答案时,删除后的长度是否和已给串长度相同
{
sum+=A[a[i]-'a'];
ans+=a[i];
if(sum>a.size()) break; //以此前缀为答案,长度大于所给长度,无解
if(sum==a.size()) //长度与所给长度相等,可能为答案
{
if(check(ans)){ //还原删除后的串,看是否与所给相同
flag=1;break;
}
}
}
if(!flag) cout<<-1<<"\n";
else cout<<ans<<" "<<s<<"\n";
}
return 0;
}
纯暴力过不了,找出最可能是答案的再暴力。好思路。