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);
}