ZYB玩字符串

问题描述
ZYB获得了一个神秘的非空字符串𝑝。
初始时,串𝑆是空的。
ZYB会执行若干次这样的操作:

  1. 选取𝑆中的一个任意的位置(可以是最前面或者最后面)
  2. 在这个位置上插入一个完整的𝑝,得到一个新的𝑆。
    但是ZYB不小心把𝑝弄丢了。
    他告诉你现在的𝑆是什么,请帮他还原出可能的𝑝。
    如果有多个𝑝符合要求,选取长度最短的。
    如果仍然有多解,选取字典序最小的。

输入格式
从文件string.in中读入数据。
这道题有多组数据,第一行一个数𝑇,表示数据组数。
对于每组数据,读入一行字符串,表示𝑆。

输出格式
输出到文件string.out中。
一共𝑇行,每行一个字符串𝑝,表示对应的答案。

样例
样例输入
1
hhehellolloelhellolo
样例输出
hello
样例解释
𝑆 为:
1.
2. hello
3. hhelloello
4. hhelloelhellolo
5. hhehellolloelhellolo
6.
数据规模与约定
前20% :|𝑆| ≤ 8
前40% :|𝑆| ≤ 20
前60% :|𝑆| ≤ 100,
Σ︀
|𝑆| ≤ 300
另有10% :𝑆是𝑝等概率插入可行位置构造出来的。
另有10% :𝑝的长度不超过3。
100% :|𝑆| ≤ 200, 𝑇 ≤ 10,
Σ︀|𝑆| ≤ 666
题解:
看到数据这么小,就可以知道枚举计算。
但枚举一个len,一个左端点,就是n^2了,再加上递归枚举删除的字串,这是我们不能接受的。
所以考虑优化判断。
可以发现,对区间的判断是可以合并的。这启发我们用区间dp
但是怎么求呢????
考虑题目中的过程,模拟一下,退而求其次,可以考虑对加入的字符(已判断的长度)作为阶段。
这样的话设
f[l][i][j]为已经判断长l时,i到j是否合法。
注意此处的合法指的是是否能被枚举的字串构造,若不能也得保证多余的恰好是字串的前缀。
又发现可以省去l这一维
这时,更新阶段即为f[i][j]=f[i][j-1]&(a[j]==b[l]);
状态合并&转移有多种可能。但对于此题来说,因为我们的便利顺序在末尾增添字符,且这道题的合法合并是必定有一方是字串长度的整数倍,所以
f[i][j] | =f[i][j-len × \times ×k] & f[j-len × \times ×k+1][j];
end

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
#define N 205
using namespace std;
int f[N][N];
map<string, int> hs;
int n;
char a[N],b[N];
int all[N],tem[N],rest[N];
inline int check(int len){
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;++i){
        f[i][i-1]=1;f[i][i]=a[i]==b[1];
    }
    for(int l=2;l<=n;++l){
        char c=b[(l-1)%len+1];
        for(int i=1;i<=n-l+1;++i){
            int j=i+l-1;
            f[i][j]=f[i][j-1]&(a[j]==c);
            for(int k=1;!f[i][j]&&k*len<=l;++k){
                f[i][j]|=f[i][j-len*k]&f[j-len*k+1][j];
            }
        }
    }
    return f[1][n];
}
void work(){
    scanf("%s",1+a);
    n=strlen(a+1);
//  cout<<n<<endl;
    memset(all,0,sizeof(all));
    string best;int found=0;
    for(int i=1;i<=n;++i)all[a[i]-'a'+1]++;
    /*for(int i=1;i<=n;++i)cout<<a[i];
    cout<<endl;*/
    for(int len=1;len<=n;++len)if(n%len==0){
        hs.clear();
        int num=n/len;
        int flag=1;
        for(int i=1;i<=26;++i){
            //if(len==5)cout<<all[i]<<endl;
            if(all[i]%num!=0){flag=0;break;}
            tem[i]=all[i]/num;
        }
    //  cout<<len<<endl;
        if(!flag)continue;
        for(int i=1;i<=n-len+1;++i){
            memcpy(rest,tem,sizeof(tem));
            flag=1;
            for(int j=0;j<len;++j){
                b[1+j]=a[i+j];
                if(--rest[b[1+j]-'a'+1]<0){flag=0;break;}
            }
            if(flag==0)continue;
            b[1+len]=0;///
            string cur=b+1;
        //  cout<<len<<endl;
            if(hs.find(cur)!=hs.end())continue;
            hs.insert(make_pair(cur,len+i));
        //  if(len==5&&cur=="hello")cout<<check(len)<<endl;
            if(check(len)){
                if(found==0)best=cur;
                else best=min(best,cur);
                found=1;
            }
        }
        if(found)break;
    }
    //string cur="hello";
//  best=min(cur,best);
    //cout<<found<<endl;
    cout<<best<<endl;
    return ;
}
int main(){
    int t;cin>>t;
    while(t--){
        work();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值