题解:P16827 [AFOI 2025] D.谐音替换
limingyuan333 · · 题解
十分钟会了,最后写的时候把整段分成三段写成一个前缀分成三段了且没什么时间调了,导致赛时没过。
题意:
给定
做法:
我们首先会想一个暴力 DP 做法,我们知道 DP 是有两种写法的,自己转移后面,或者自己被前面转移,我们从这里得到启发,我们发现转移后面相当于在一个前缀上加字母,被前面转移相当于往左走的时候在后缀上加字母,且我们知道前后缀是存在单调性的,于是我们考虑结合两种做法。
首先我们先把前面
- 我们转移后面的时候直接写个哈希二分,查一下当前这子串在前缀中有没有存过,然后我们发现截至到我们二分到的最后一个位置,这中间的位置都是可以转移的。
- 我们被前面转移时也同理,找到最前面存在的后缀。
- 我们发现这样的话,可能存在一个子串既是后缀还是前缀的可能,一个位置会在转移时可能被同一个位置计算两次,不过这个也容易解决。我们可以开两个 bit 维护一下,强制一个取满,然后另一个取得少一点。
- 具体的,我们对原序列扫描线,然后扫描线时加入操作一中的线段,然后我们用 bit 实时维护每个位置的值。第二次操作时,我们假设当前位置是
i ,我们二分出一个最长子串假设为j ,我们强制后面的左端点是j \sim i 的点在第二段转移中出现,其余点在第一段转移中出现,这样就不会算重了。
代码很好写都是复制粘贴的。
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
using namespace std;
const int MAXN=5e5+10;
const int base=131311;
int n,m,len;
struct BIT{
int t[MAXN];
int lowbit(int x){return x&-x;}
void add(int x,int y){
for(int i=x;i<=len;i+=lowbit(i)) t[i]+=y;
}
int sum(int x){
int res=0;
if(!x) return 0;
for(int i=x;i;i-=lowbit(i)) res+=t[i];
return res;
}
void clear(){
for(int i=1;i<=len;i++) t[i]=0;
}
}bit,bit2;
unordered_map<ull,bool>pre,suf;
ull Pre[MAXN],pw[MAXN];
ull get_hash(int l,int r){
return Pre[r]-Pre[l-1]*pw[r-l+1];
}
vector<int>Add[MAXN],Del[MAXN];
int vis[MAXN],s[MAXN];
signed main(){
ios::sync_with_stdio(false);
cin.tie(NULL),cout.tie(NULL);
cin>>n>>m;pw[0]=1;
for(int i=1;i<=MAXN-10;i++) pw[i]=pw[i-1]*base;
for(int i=1;i<=n;i++){
string s;cin>>s;
ull hs=0;
for(int j=0;j<s.size();j++){
hs=hs*base+s[j];pre[hs]=1;
}hs=0;ull P=1;
for(int j=s.size()-1;j>=0;j--){
hs=hs+P*s[j];suf[hs]=1;P*=base;
}
}
for(int i=1;i<=m;i++){
string t;cin>>t;t=" "+t;len=t.size()-1;
bit.clear();bit2.clear();
for(int j=1;j<=len;j++){
s[j]=0;vis[j]=0;
Pre[j]=Pre[j-1]*base+t[j];
if(pre[Pre[j]]||suf[Pre[j]]) bit.add(j,1),vis[j]=1;
}
for(int j=1;j<len-1;j++){
if(vis[j]){
int l=j+1,r=len-1,num=j;
while(l<=r){
int mid=l+r>>1;
if(pre[get_hash(j+1,mid)]) num=mid,l=mid+1;
else r=mid-1;
}
if(num>j) Add[j+1].push_back(j),Del[num].push_back(j);
}vis[j]=0;
}
for(int j=2;j<len;j++){
for(auto x:Add[j]) bit2.add(x,1);
int l=2,r=j,num=j+1;
while(l<=r){
int mid=l+r>>1;
if(suf[get_hash(mid,j)]) num=mid,r=mid-1;
else l=mid+1;
}
s[j]=bit2.sum(num-2);
s[j]+=bit.sum(j-1)-bit.sum(num-2);
for(auto x:Del[j]) bit2.add(x,-1);
Add[j].clear();Del[j].clear();
}bit2.clear();bit.clear();
for(int j=2;j<len;j++){
if(s[j]){
int l=j+1,r=len,num=j;
while(l<=r){
int mid=l+r>>1;
if(pre[get_hash(j+1,mid)]) num=mid,l=mid+1;
else r=mid-1;
} if(num>j&&num==len) Add[j+1].push_back(j),Del[num].push_back(j);
bit.add(j,s[j]);
}
}
int ans=0;
for(int j=3;j<=len;j++){
for(auto x:Add[j]) bit2.add(x,s[x]);
if(j==len){
int l=3,r=j,num=j+1;
while(l<=r){
int mid=l+r>>1;
if(suf[get_hash(mid,j)]) num=mid,r=mid-1;
else l=mid+1;
}
ans+=bit2.sum(num-2),ans+=bit.sum(j-1)-bit.sum(num-2);
}
for(auto x:Del[j]) bit2.add(x,-s[x]);
Add[j].clear();Del[j].clear();
}cout<<ans<<'\n';
}
return 0;
}