字符串杂题
P1709 [USACO5.5] 隐藏口令 Hidden Password
实际就是求最小表示法。首先一个直观的思路是我们将最小的字符存下来然后进行比较,但是如果是"aaa···aaa"仍然会被卡成\(O(N^2)\).考虑优化,发现如果我们记下每个最小字符之后第一个大于这个字符的字符,那么我们就可以判掉这种情况,但是如果遇见"abab···ababab"仍然会炸.考虑我们的瓶颈(也就是为什么复杂度会这么高即做了哪些重复工作),是我们对于以每个位子开头的字符串都要比较一次,有一个关于字符串算法的核心思想:
基于所求信息的特殊性质与已经求出的信息,使用增量法与势能分析求得所有信息。这体现了动态规划思想。
显然这对于解字符串类型的题目也有参考价值.我们考虑如果我们比较了以i,j两个位置开头的字符串,那么可以省略什么,发现如果第p次出现不同.即s[i+p]≠s[j+p],不妨令s[i+p]>s[j+p],那么我们发现i ~ i+p之间的位置一定不是答案,因为s[i ~ i+p],与s[j ~ j+p]一样,所以如果有一个答案以s[i+k]开头(0<=k<=P),那么s[j+k]开头的一定更小.
所以我们用两个指针,p,q开始指向0,1.枚举k每次比较,如果p+k<q+k,那么q变为q+k+1,至于循环问题,破环成链一下就好.时间复杂度O(N)
注意如果发现p==q,要手动把q+1,还有要保证l<r
详细算法内容
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_
#define P pair<ll,ll>
string s;
ll N;
int main()
{
// freopen("P1709.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>N;
char ch;
while(cin>>ch)
{
if(isspace(ch))continue;
s+=ch;
}
s+=s;
ll l=0,r=1;
while(1)
{
bool f=1;
for(int i=0;i<N;i++)
{
if(s[l+i]<s[r+i]){
r=r+i+1;
f=0;
break;
}
if(s[l+i]>s[r+i]){
l=l+i+1;
if(l==r)r++;
f=0;
break;
}
}
if(f)r++;
if(l>r)swap(l,r);
// cout<<l<<" "<<r<<endl;
if(r>=N)break;
}
cout<<l;
return 0;
}
小结:关于字符串核心思想要掌握,暴力优化考虑重复做了哪些事
P2102 地砖铺设
一个贪心题,然后暴力模拟即可.
小结:能暴力就暴力,先保证正确性
P3426 [POI 2005] SZA-Template
这种匹配问题首先考虑的肯定是kmp,但是我们发现这个是可以重复的匹配。如果是暴力,那么要NNN,根据字符串的核心思想,我们想到dp。那么因为是kmp我们大胆猜测,答案可能是broader或本身,现在问题变成如何用前缀信息解决i。如果答案是bd,那么i的bd的bd一定是i的bd,所以答案可能包含dp[j](其中j是i的bd),那么根据一系列证明我们发现猜测是正确的(具体证明比较繁琐,笔者不是很懂)
那么,dp[\(bd_{max}\)]如何转移到dp[i]呢,我们记h[u]表示前缀u可以贡献到的最远距离,那么如果h[dp[\(bd_{max}\)]]>\(i-bd_max\),也就是大于其最大的循环,那么就可以贡献,如下图

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll>
#define N_ 1000055
ll f[N_],h[N_],p[N_];
char S[N_];
int main()
{
cin>>S+1;
ll N=strlen(S+1);
for(int j=0,i=2;i<=N;i++){
while(j&&S[j+1]!=S[i])j=p[j];
p[i]=j+=(S[i]==S[j+1]);
}
f[0]=1;
for(int i=1;i<=N;i++)
{
f[i]=i;
if(h[f[p[i]]]>=i-p[i])f[i]=f[p[i]];
h[f[i]]=i;
}
cout<<f[N];
return 0;
}
关于用kmp匹配字符串
要先将模式串做一次kmp求出p数组,然后文本串再根据p数组匹配模式串
同理
考虑用kmp求一次模式串,然后文本串匹配一次模式串,匹配过程中如果匹配次数大于模式串长度,就连续出栈,否则就入栈
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define P pair<ll,ll>
#define N_ 1000005
string S,T;
ll p[N_],H[N_],top,q[N_];
int main()
{
cin>>S>>T;
for(int i=1,j=0;i<T.size();i++)
{
while(j&&T[i]!=T[j])j=p[j-1];
p[i]=j+=(T[i]==T[j]);
}
for(int i=0,j=0;i<S.size();i++)
{
while(j&&T[j]!=S[i])j=p[j-1];
H[i]=j+=(T[j]==S[i]);
if(j>=T.size())
{
top-=j;
top++;
j=H[q[top]];
// cout<<q[top]<<endl;
}
else
{
++top,q[top]=i;
}
}
// for(int i=0;i<S.size();i++)
// cout<<H[i]<<" ";
// cout<<endl;
// for(int i=1;i<=top;i++)
// cout<<q[top]<<" ";
// cout<<endl;
for(int i=1;i<=top;i++)
cout<<S[q[i]];
return 0;
}
小结:发现这种匹配就删除的东西,一般要用栈
KMP好题
首先一个暴力思路,是直接暴力跳p数组,直到长度小于前缀长度一半,但是如果题目数据是1e6个a,就会被卡成N*N,考虑优化
1.发现这种东西非常可以用倍增优化,具体的将每个位置跳\(2^k\)的j求出来,那么查询的时候,就可以log时间查
2.发现每次条p数组很浪费,考虑到字符串核心思想,我们可以想到每次最终j都会i/2,那我们不妨保留j,这样下一次就可以不用重复跳
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 1000005
ll T,p[N_],cnt[N_];
string S;
int main()
{
cin>>T;
while(T--)
{
cin>>S;
cnt[1]=1;
for(int j=0,i=1;i<S.size();i++)
{
while(j&&S[i]!=S[j])j=p[j-1];
p[i]=j+=(S[i]==S[j]);
cnt[i+1]=cnt[j]+1;
}
ll ans=1;
for(int i=0,j=0;i<S.size();i++)
{
while(j&&S[i]!=S[j])j=p[j-1];
j+=(S[i]==S[j]);
while(j>(i+1)/2)j=p[j-1];
ans=ans*(cnt[j]+1)%1000000007;
}
cout<<ans<<'\n';
memset(p,0,sizeof(p));
memset(cnt,0,sizeof(cnt));
}
return 0;
}
小结:字符串核心思想

浙公网安备 33010602011771号