【codeforces上紫】字符串

D2. Prefix-Suffix Palindrome (Hard version)

题意:给一个字符串,求把他的前缀和后缀拼在一起得到的最长回文串。多个解则输出一解。

Solution1:
所以用hash函数解决(预处理从左到右和从右到左的字符串哈希。枚举对称中心O(n),O(1)计算子串哈希值,比较正着算的哈希是否等于反着算的哈希(O(1)))。(待补)

Solution2:
将开头与结尾相同的拿掉之后,直接用Manacher算法求最左和最右的最长回文串。

Solution3:
将开头与结尾相同的拿掉之后,只需要找中间这部分的开头部分或者结尾部分的最长回文串即可,那么可以考虑这样一种方法:
假设中间部分的字符串为s1,用"------>“代表正序,将s1翻转过来得到s2,用”<------"表示;
求s1的前缀部分的最长回文串,可以这样写,s1+s2 = “------><------”,只需要用前缀函数可以直接得到前缀最长回文串的长度。
而求s1的后缀部分的最长回文串,可以求s2+s1 = “<------------>”,也只需要用前缀函数可以直接得到后缀最长回文串的长度。
但要注意一个点,如果s1+s2和s2+s1形成了可以由循环节循环而成的字符串的话,这样得出的是不对的答案,其实只需要保证不会形成循环节即可,
怎么保证呢?可以让s1+s2 = “------>#<------”,让s2+s1 = “<------#------>”,这样,通过前缀函数求出的就一定是正确答案了。

sol2

#include<bits/stdc++.h>
using namespace std;

string Manacher(string s){
    //cout<<s<<endl;
    string t="$#";
    for(int i=0;i<s.size();++i) t+=s[i],t+='#';
    vector<int> p(t.size(),0);
    int mx=0,id=0,reslen=0,rescenter=0;
    for(int i=1;i<t.size();++i){
        p[i]=mx>i?min(p[2*id-i],mx-i):1;
        while(t[i+p[i]]==t[i-p[i]]) ++p[i];
        if(mx<i+p[i]) mx=i+p[i],id=i;
        if(reslen<p[i]&&
            (((i-p[i])/2==0)||(((i-p[i])/2+p[i]-2)==s.size()-1))){
            reslen=p[i];
            rescenter=i;
        }
    }
    return s.substr((rescenter-reslen)/2,reslen-1);
}

void solve(string s){
    string ans=Manacher(s);
    cout<<ans;
}

int main(){
    int t;cin>>t;
    while(t--){
        string s;cin>>s;
        int j=0,sz=s.size();
        while(j<sz-j-1&&s[j]==s[sz-j-1]) cout<<s[j++];
        if(2*j<sz) solve(s.substr(j,sz-2*j));
        while(j>0) cout<<s[--j];
        cout<<"\n";
    }
}

sol3

#include<bits/stdc++.h>
using namespace std;

void getfail(string p,int *f){
    int m=p.length();
    f[0]=f[1]=0;
    for(int i=1;i<m;++i){
        int j=f[i];
        while(j&&p[i]!=p[j]) j=f[j];
        f[i+1]=p[i]==p[j]?j+1:0;
    }
}

int f[2000005];
void solve(string s){
    string revs=s;
    reverse(revs.begin(),revs.end());
    string ans=s+"#"+revs;
    getfail(ans,f);
    int a1,a2;
    a1=f[ans.length()];
    ans=revs+"#"+s;
    getfail(ans,f);
    a2=f[ans.length()];
    if(a1>a2) ans=s.substr(0,a1);
    else ans=revs.substr(0,a2);
    cout<<ans;
}

int main(){
    int t;cin>>t;
    while(t--){
        string s;cin>>s;
        int j=0,sz=s.size();
        while(j<sz-j-1&&s[j]==s[sz-j-1]) cout<<s[j++];
        if(2*j<sz) solve(s.substr(j,sz-2*j));
        while(j>0) cout<<s[--j];
        cout<<"\n";
    }
}

E. A Bit Similar

题意:给一个长度为n的01字符串s。让你求出一个字典序最小的,长度为k的字符串,使得这个新字符串t,与s所有长度为k的子串都满足a bit similar。a bit similar的定义是,两个串存在某一对应位置,都为0或都为1(s1[i]=s2[i]).
思路:首先,明确一点:s的所有长度为k的子串,都在排除一个可能的答案。即与这个子串完全相反的子串。因此,最多排除n-k+1个子串。我们把n-k+1设为z。既然最多排除n-k+1个子串,那么在字典序最小的n-k+2个子串(即z+1)中绝对存在答案。因此,我们可以保证答案的前k-ceil(log(z+1))个字符为0. 如果排除的子串数目大于2^k怎么办?那么无解。可以证明,这种情况下,k一定比较小。
因此我们对每一个s的长度为k的子串进行哈希,记录他的反串。然后从全为0的字符串开始,按照字典序递增,找到第一个没有被记录的串。就是答案。

如何哈希求反串?这里有两种做法。
sol1:题解做法。把字符串当作二进制数,把二进制转换为十进制。那么反串就是11111。。11-二进制。这样做的基础是,证明了前面为0的字符串的后面部分最长为ceil(log(z+1)),变成数字后小于int范围。

sol2:直接全部哈希。哈希的反串为全为1的数字的哈希值减去当前串的哈希值。
sol1:题解做法

#include<bits/stdc++.h>
using namespace std;
int ceillog(int x){
    int y=0;
    while((1<<y)<x)
        y++;
    return y;
}

int main(){
    int q;cin>>q;
    while(q--){
        int n,k;
        cin>>n>>k;
        string s;
        cin>>s;
        int m=min(k,ceillog(n-k+2));
        // 只用判定这个长度即可。
        vector<int>used((1<<m),0);
        vector<int>back0(n,int(1e9)); // 对于每个字符串的每个位置,其后的0的位置。
        for(int i=n-1;i>=0;--i){
            if(s[i]==0) back0[i]=i;
            else if(i!=n-1) back0[i]=back0[i+1];
        }
        int d=k-m;// 不用判定的长度
        for(int i=0;i<n-k+1;++i){
            if(back0[i]-i<d) continue;
            int cur=0;
            for(int j=i+d;j<i+k;++j)
                cur=cur*2+s[j]-'0';
            used[((1<<m)-1)^cur]=1;
        }
        int ans=-1;
        for(int i=0;i<(1<<m);++i){
            if(used[i]==0){ans=i;break;}
        }
        if(ans==-1) cout<<"NO"<<endl;
        else {
            cout<<"YES"<<endl;
            string res(d,'0');
            string res2;
            for(int i=0;i<m;++i){
                res2.push_back('0'+(ans%2));
                ans/=2;
            }
            reverse(res2.begin(),res2.end());
            res+=res2;
            cout<<res<<endl;
        }
    }
}

sol2:
暴力哈希做法。

#include<bits/stdc++.h>
using namespace std;
/*
 * 思路:
 * 哈希。
 */
typedef unsigned long long ull;
const int maxn = 1e6+4;
const int mod = 1601202031;
char s[maxn];
int ans[maxn];
ull bas[maxn],has[maxn];
set<ull> S;

ull gethash(int l,int r){
    return has[r]-has[l-1]*bas[r-l+1];
}

bool solve(int k,ull &h){
    int pos=1;
    while(pos<=k&&ans[pos]==1){
        ans[pos]=0;
        h-=bas[pos-1];
        pos++;
    }
    if(pos>k) return false;
    ans[pos]=1;
    h+=bas[pos-1];
    return true;
}

int main(){
    int q;cin>>q;
    bas[0]=1; for(int i=1;i<maxn;++i) bas[i]=bas[i-1]*mod;
    while(q--){
        memset(ans,0,sizeof(ans));
        S.clear();
        int n,k;cin>>n>>k;
        scanf("%s",s+1);
        for(int i=1;i<=n;++i) has[i]=has[i-1]*mod+s[i]-'0';
        ull ops=0; for(int i=0;i<=k-1;++i) ops+=bas[i];

        for(int i=1;i<=n-k+1;++i){
            ull h=gethash(i,i+k-1);
            h=ops-h;
            S.insert(h);
        }

        int flg=0;ull cur=0;  // 全是0的字符串的哈希值也是0.
        while(1){
            if(S.find(cur)==S.end()) break;
            if(!solve(k,cur)) {flg=1;break;}
        }
        if(flg) cout<<"NO"<<endl;
        else {
            cout<<"YES"<<endl;
            for(int i=k;i>=1;--i) cout<<ans[i];
            cout<<endl;
        }
    }
}

哈希板子

#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+5;
char s[maxn];
typedef long long ll;

// 自然溢出会被卡啊....well

const int mod1 = 1e9+7;
const int mod2 = 998244353;
const int p = 101;

ll base1[maxn],base2[maxn];
ll hs1[maxn],hs2[maxn];

map<pair<ll,ll>,int> mp,mp2;  // 计数菌

pair<ll,ll> subs(int l,int r){
    ll ans1=(hs1[r]+mod1-hs1[l-1]*base1[r-l+1]%mod1)%mod1;
    ll ans2=(hs2[r]+mod2-hs2[l-1]*base2[r-l+1]%mod2)%mod2;
    return make_pair(ans1,ans2);
}

int main(){
    int n;scanf("%d",&n);
    // 初始化处理hash
    // 为了防止crash,用两个模数
    base1[0]=1;
    base2[0]=1;
    for(int i=1;i<=maxn;++i){
        base1[i]=base1[i-1]*p%mod1;
        base2[i]=base2[i-1]*p%mod2;
    }
    ll ans = 0;
    while(n--){
        scanf("%s",s+1);
        int len = strlen(s+1);
        hs1[0]=0;hs2[0]=0;
        for(int i=1;i<=len;++i){
            hs1[i] = (hs1[i-1]*p+s[i]-'a'+1)%mod1;
            hs2[i] = (hs2[i-1]*p+s[i]-'a'+1)%mod2;
        }
        pair<ll,ll> o = make_pair(hs1[len],hs2[len]);
        ans += mp[o];
        mp[o]++;
        ans += mp2[o]; // 加上中间是这样的
        for(int i=1;2*i<len;++i){
            // 1 - i, len-i+1 - len
            o = subs(1,i);
            pair<ll,ll> o2 = subs(len-i+1,len);
            if(o==o2){
                // 前后缀一样
                o = subs(i+1,len-i);
                // 中间部分
                ans += mp[o]; // 加上整个人就是中间的
                mp2[o]++;
            }
        }
    }
    printf("%lld\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值