字符串专题
字符串专题
A - Security
这是平凡题...(不是我说的,是神说的)
我的做法比较大众化,追求更高级的做法去看神的博客吧。
题意很好理解,不再赘述。这里做法是SA加平凡的线段树。
将询问离线,把每个字符串拼到s后面,一遍SA后,得到每个后缀的rak。我们发现,对于s的一个子串a和询问的一个字符串b,若
显然可以由后缀判断,这样就把比较子串转化为比较后缀。
对于后缀i,得到一个二元组
枚举lcp,按照上述思路二分,得到每个询问的每个lcp的区间(以rak为序),就是说,rak值不在区间
CODE
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+100;
int n,q,m;
char s[N],X[N],t[N];
int sa[N],rak[N],tp[N],tax[N],height[N],st[30][N],lg[N];
int tree[N<<2],tag[N];
struct ques{
int l,r,id,len,num;
string s;
}ask[N];
struct node{
int _,lcp,_end;
};
struct answer{
int lcp,num;
}ans[N];
vector<node>v[N];
void qsort(){
memset(tax,0,sizeof(tax));
for(int i=1;i<=n;i++)tax[rak[i]]++;
for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
for(int i=n;i>=1;i--)sa[tax[rak[tp[i]]]--]=tp[i];
}
void suffixsort(){
m=200;
for(int i=1;i<=n;i++){
rak[i]=s[i];
tp[i]=i;
}
qsort();
for(int w=1,p=0;p<n;w<<=1,m=p){
p=0;
for(int i=1;i<=w;i++)tp[++p]=n-w+i;
for(int i=1;i<=n;i++)
if(sa[i]>w)tp[++p]=sa[i]-w;
qsort();swap(rak,tp);
rak[sa[1]]=p=1;
for(int i=2;i<=n;i++){
rak[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
}
}
}
void get_height(){
int j,k=0;
for(int i=1;i<=n;i++){
if(k)k--;
j=sa[rak[i]-1];
while(s[i+k]==s[j+k])k++;
height[rak[i]]=k;
}
for(int i=1;i<=n;i++){
st[0][i]=height[i];
}
for(int i=1;i<=lg[n]+1;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
}
}
inline int get(int x,int y){
int llg=lg[y-x+1];
return min(st[llg][x],st[llg][y-(1<<llg)+1]);
}
int find(int x,int y){
int left=x,right=n,mid,ans=x;
while(left<right){
mid=(left+right)>>1;
if(get(x,mid)>=y)left=mid+1;
else right=mid;
}
if(get(x,left)>=y)return left+1;
return left;
}
int find_back(int x,int y){
int left=1,right=x,mid;
while(left<right){
mid=(left+right)>>1;
if(get(mid+1,x)<y)left=mid+1;
else right=mid;
}
return left;
}
#define lson (st<<1)
#define rson (st<<1|1)
inline void pushup(int st){
if(tree[lson]&&tree[rson])tree[st]=(rak[tree[lson]]<rak[tree[rson]]?tree[lson]:tree[rson]);
else if(tree[lson])tree[st]=tree[lson];
else tree[st]=tree[rson];
}
void build(int st,int l,int r){
if(l==r){
tree[st]=l;
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(st);
}
void updata(int st,int l,int r,int x){
if(l==r){
tree[st]=0;
return ;
}
int mid=(l+r)>>1;
if(mid>=x)updata(lson,l,mid,x);
else updata(rson,mid+1,r,x);
pushup(st);
}
int query(int st,int l,int r,int L,int R){
if(L<=l&&r<=R){
return tree[st];
}
int mid=(l+r)>>1,ans=0,k;
if(mid>=L){
k=query(lson,l,mid,L,R);
if(k)ans=k;
}
if(mid+1<=R){
k=query(rson,mid+1,r,L,R);
if(!ans||(k&&rak[ans]>rak[k]))ans=k;
}
return ans;
}
void print(int x,int y,int z){
int i;
for(i=1;i<=z;i++){
putchar(s[x+i-1]);
if(s[x+i-1]>s[y+i-1])break;
}
if(i==z+1)putchar(s[x+i-1]);
puts("");
}
signed main()
{
scanf("%s",t+1);
n=strlen(t+1);
for(int i=1;i<=n;i++){
s[i]=t[i];
}
scanf("%d",&q);
for(int i=2;i<N;i++){
lg[i]=lg[i>>1]+1;
}
for(int i=1;i<=q;i++){
scanf("%d%d",&ask[i].l,&ask[i].r);
scanf("%s",X+1);
int l=n;
ask[i].id=n+1;
tag[n+1]=i;
ask[i].num=i;
ask[i].len=strlen(X+1);
for(int j=1;j<=ask[i].len;j++){
s[l+j]=X[j];
}
n+=ask[i].len;
}
suffixsort();
get_height();
build(1,1,n);
for(int i=1;i<=n;i++){
if(tag[sa[i]]){
int k=tag[sa[i]];
for(int j=0;j<=ask[k].len;j++){
int _begin=find(i+1,j+1),_end=find(i+1,j)-1;
if(get(i+1,_begin)!=j||_begin>_end)continue;
v[_begin].push_back({k,j,_end});
}
if(i!=1){
v[find_back(i,ask[k].len)].push_back({k,ask[k].len,i});
}
if(i!=n){
v[i].push_back({k,ask[k].len,find(i+1,ask[k].len)-1});
}
//二分得到所有可能区间
}
}
for(int i=1;i<=n;i++){
for(auto j:v[i]){
if(ask[j._].l>ask[j._].r-j.lcp)continue;
int k=query(1,1,n,ask[j._].l,ask[j._].r-j.lcp);
if(k==0)continue;
if(rak[k]>j._end)continue;//超过区间上界,非法解
if(!ans[j._].num)ans[j._]={j.lcp,k};
else if(j.lcp>ans[j._].lcp)ans[j._]={j.lcp,k};
else if(j.lcp==ans[j._].lcp&&rak[k]<rak[ans[j._].num])ans[j._]={j.lcp,k};//保存答案
}
updata(1,1,n,sa[i]);
}
for(int i=1;i<=q;i++){
if(!ans[i].num){
puts("-1");
continue;
}
print(ans[i].num,ask[i].id,ask[i].len);//输出答案
}
}
B - Yet Another LCP Problem
相似题目差异
题目主要内容就是求
利用后缀自动机,并求height数组。
我们发现,统计答案的先后顺序是没有影响的,我们不妨用排序后的顺序统计。
发现这是具有单调性的。
我们用单调栈维护一段的最小height,直接扫过去,统计答案即可。
注意特判空栈就好。复杂度
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+100;
int n,q,k,l,a,b,m;
char s[N];
int sa[N],rak[N],tax[N],tp[N],sum,ans,height[N];
int qs[N];
struct solve{
int id,val;
};
stack<solve>st;
void qsort(){
memset(tax,0,sizeof(tax));
for(int i=1;i<=n;i++)tax[rak[i]]++;
for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
for(int i=n;i>=1;i--)sa[tax[rak[tp[i]]]--]=tp[i];
}
void suffixsort(){
m=200;
for(int i=1;i<=n;i++){
rak[i]=s[i];
tp[i]=i;
}
qsort();
for(int w=1,p=0;p<n;w<<=1,m=p){
p=0;
for(int i=1;i<=w;i++)tp[++p]=n-w+i;
for(int i=1;i<=n;i++)
if(sa[i]>w)tp[++p]=sa[i]-w;
qsort();swap(rak,tp);
rak[sa[1]]=p=1;
for(int i=2;i<=n;i++){
rak[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
}
}
}
void get_height(){
int j,k=0;
for(int i=1;i<=n;i++){
if(k)k--;
j=sa[rak[i]-1];
while(s[i+k]==s[j+k])k++;
height[rak[i]]=k;
}
}
signed main()
{
scanf("%s",s+1);
n=strlen(s+1);
suffixsort();
get_height();
for(int i=n;i>=1;i--){
qs[i]=qs[i+1]+i;
}
for(int i=1;i<n;i++)ans+=qs[i+1];
for(int i=1;i<n;i++)ans+=i*(n-i);
for(int i=1;i<=n;i++){
while(!st.empty()&&st.top().val>height[i]){
solve k=st.top();
st.pop();
sum-=k.val*(k.id-st.top().id);
}
if(!st.empty())
sum+=height[i]*(i-st.top().id);
else sum+=height[i]*(i-1);
st.push(solve{i,height[i]});
ans-=2*sum;
}
cout<<ans<<endl;
}
正解
通过上一题后,这题就好理解多了。
我们将a数组和b数组合并为一个大数组,由于不一定连贯,我们用ST表处理每两个之间height最小值,然后类似于上一题的做法,最后还要去重,因为贡献中多计算a与自身和b与自身。
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+100;
int n,q,k,l,a[N],b[N],m;
char s[N];
int sa[N],rak[N],tax[N],tp[N],height[N];
int v[N];
int st[20][N],lg[N];
int sum,ans;
int minn[N];
stack<int>sta;
void qsort(){
memset(tax,0,sizeof(tax));
for(int i=1;i<=n;i++)tax[rak[i]]++;
for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
for(int i=n;i>=1;i--)sa[tax[rak[tp[i]]]--]=tp[i];
}
void suffixsort(){
m=200;
for(int i=1;i<=n;i++){
rak[i]=s[i];
tp[i]=i;
}
qsort();
for(int w=1,p=0;p<n;w<<=1,m=p){
p=0;
for(int i=1;i<=w;i++)tp[++p]=n-w+i;
for(int i=1;i<=n;i++)
if(sa[i]>w)tp[++p]=sa[i]-w;
qsort();swap(rak,tp);
rak[sa[1]]=p=1;
for(int i=2;i<=n;i++){
rak[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
}
}
}
void get_height(){
int j,k=0;
for(int i=1;i<=n;i++){
if(k)k--;
j=sa[rak[i]-1];
while(s[i+k]==s[j+k])k++;
height[rak[i]]=k;
}
}
bool cmp(int x,int y){
return rak[x]<rak[y];
}
int solve(int x[],int len){
int w;
w=lg[x[1]];
minn[1]=min(st[w][1],st[w][x[1]-(1<<w)]);
for(int i=2;i<=len;i++){
w=lg[x[i]-x[i-1]];
if(x[i]==x[i-1])minn[i]=n-sa[x[i]]+1;
else
minn[i]=min(st[w][x[i-1]+1],st[w][x[i]-(1<<w)+1]);
}
// for(int i=1;i<=len;i++)cout<<minn[i]<<endl;
while(!sta.empty())sta.pop();
m=sum=0;
for(int i=1;i<=len;i++){
while(!sta.empty()&&minn[sta.top()]>minn[i]){
int w=sta.top();
sta.pop();
sum-=minn[w]*(w-sta.top());
}
if(!sta.empty())sum+=minn[i]*(i-sta.top());
else sum+=minn[i]*(i-1);
sta.push(i);
m+=sum;
}
return m;
}
signed main()
{
scanf("%d%d",&n,&q);
scanf("%s",s+1);
suffixsort();
get_height();
for(int i=2;i<N;i++){
lg[i]=lg[i>>1]+1;
}
for(int i=1;i<=n;i++){
st[0][i]=height[i];
}
for(int i=1;i<=lg[n]+1;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
}
while(q--){
scanf("%d%d",&k,&l);
for(int i=1;i<=k;i++){
scanf("%d",&a[i]);
v[i]=a[i];
}
for(int i=1;i<=l;i++){
scanf("%d",&b[i]);
v[k+i]=b[i];
}
sort(v+1,v+k+l+1,cmp);
sort(a+1,a+k+1,cmp);
sort(b+1,b+l+1,cmp);
for(int i=1;i<=k;i++)a[i]=rak[a[i]];
for(int i=1;i<=l;i++)b[i]=rak[b[i]];
for(int i=1;i<=k+l;i++)v[i]=rak[v[i]];
printf("%lld\n",solve(v,k+l)-solve(a,k)-solve(b,l));
}
}
G - x-prime Substrings
AC自动机+DP
发现X非常小,我们枚举所有可能的"x-prime"串,放在AC自动机上,并拿字符串s匹配,如果一个节点是某个"x-prime"串的最后一个字符,打上flag标记。现在问题成为:s在自动机上匹配,不能经过flag标记,最少需要删掉几个字符。
有以下DP方程:
当前节点可以由其父亲节点转移,也可以删除当前节点,由自身转移。
可以特判根节点的情况。
CODE
#include<bits/stdc++.h>
using namespace std;
const int N=1100;
int x,n;
char s[N];
vector<int>h_pri;
char t[N<<2][N],k[N];
int p,num,len[N<<2],dp[N][N<<3];
int cnt=1;
struct AC{
int son[30],fail;
bool flag;
}trie[N<<5];
void dfs(int tot){
if(tot==x){
for(int i=1;i<=p;i++){
int sum=0;
for(int j=i;j<=p;j++){
sum+=k[j]-'0';
if(x%sum==0&&x!=sum)return ;
}
}
num++;
for(int i=1;i<=p;i++){
t[num][i]=k[i];
}
len[num]=p;
}
if(tot>=x)return ;
for(auto i:h_pri){
k[++p]='0'+i;
dfs(tot+i);
p--;
}
}
void insert(int a){
int u=1;
for(int i=1;i<=len[a];i++){
if(!trie[u].son[t[a][i]-'0']){
trie[u].son[t[a][i]-'0']=++cnt;
}
u=trie[u].son[t[a][i]-'0'];
}
trie[u].flag=1;
}
void getfail(){
for(int i=1;i<=9;i++)trie[0].son[i]=1;
queue<int>q;
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=1;i<=9;i++){
int v=trie[u].son[i];
int fail=trie[u].fail;
if(!v){
trie[u].son[i]=trie[fail].son[i];
continue;
}
trie[v].fail=trie[fail].son[i];
q.push(v);
}
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
scanf("%d",&x);
for(int i=2;i<=min(x,9);i++){
if(x%i!=0){
h_pri.push_back(i);
}
}
if(x<=9)h_pri.push_back(x);
dfs(0);
for(int i=1;i<=num;i++){
insert(i);
}
getfail();
memset(dp,0x3f,sizeof(dp));
dp[0][1]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=cnt;j++){
int v=trie[j].son[s[i]-'0'];
dp[i][j]=min(dp[i][j],dp[i-1][j]+1);
if(j!=1){
if(!trie[v].flag)
dp[i][v]=min(dp[i][v],dp[i-1][j]);
}
else{
if(v){
if(!trie[v].flag)
dp[i][v]=min(dp[i][v],dp[i-1][j]);
}
else dp[i][1]=min(dp[i][1],dp[i-1][1]+1);
}
}
}
int ans=INT_MAX;
for(int i=1;i<=cnt;++i) ans=min(ans,dp[n][i]);
cout<<ans<<'\n';
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】