P3082 [USACO13MAR] Necklace G
闲聊:警示后人,\(dp_{i,m}\) 为不合法状态。
本题解是在读了题解区其他 dalao 的题解后,根据我个人的理解进行了一些补充说明后写出来的,算是比较详尽。
(以下称项链串为 \(s1\) 或文本串,名字串为 \(s2\) 或模式串)
首先,最少删除字符个数似乎不是很好做,我们把它转化成最多保留多少字符。
其次,本题涉及字符串匹配问题,我们可能要考虑哈希或 KMP。
那这是一个最值类问题, 我们考虑 dp。设 \(dp_{i,j}\) 表示当前文本串的前 \(i\) 位已经匹配完毕,而且此时匹配到了模式串第 \(j\) 位,最多能保留多少字符。
填表法转移似乎不简单,不如考虑刷表法,即考虑 \(dp_{i,j}\) 可以转移到哪里。
前 \(i\) 位已经匹配完了,那我们就看看,考虑 \(s1_{i+1}\) 后会出现什么情况。
第一种情况,我们舍弃 \(s1_{i+1}\),那能转移到的状态就是 \(dp_{i+1,j}\),此时 \(dp_{i+1,j}= \max (dp_{i+1,j},dp_{i,j})\)。
第二种情况,我们保留 \(s1_{i+1}\),并让它参与匹配。假设它参与匹配后能匹配到第 \(k\) 位,那么能转移到的状态就是 \(dp_{i+1,k}\),此时 \(dp_{i+1,k}= \max (dp_{i+1,k},dp_{i,j}+1)\)。
但是,这个 \(k\) 怎么处理?于是我们想办法另开一个辅助数组 \(g_{i,j}\) 表示已经匹配上了模式串的前 \(i\) 位,当前要加入文本串的一个字符 \(j\) 后继续匹配,能匹配到模式串第几位。它就是卡着刚才 \(k\) 的定义量身打造的。
第一种情况很好想,模式串的第 \(i+1\) 位恰好是字符 \(j\),此时 \(g_{i,j}=i+1\)。
第二种情况,两个位置的字符没有匹配上,我们联想到 KMP 算法里那个失配的情况,它俩几乎是一模一样的:KMP 正是在考虑加入文本串 \(j\) 字符时失配的情况下,模式串的指针该滑到哪里。我们照着 KMP 里的这种情况转移,令 \(g_{nxt_i,j}\)。
可能有所不同的是,KMP 里面是不断让 \(j=nxt_{j}\),跳到自己最长 border 的结束位置,但是 \(g_{i,j}=g_{nxt_i,j}\) 只赋值一次即可,因为继续往下跳的情况 \(g_{nxt_i,j}\) 已经考虑完了。
总之我们的 \(g\) 数组处理完了,\(k\) 就能表示为 \(g_{j,s1_{i+1}}\),\(dp\) 第二种情况的转移方程式为 \(dp_{i+1,g_{j,s1_{i+1}}}= \max (dp_{i+1,g_{j,s1_{i+1}}},dp_{i,j}+1)\)。
代码:
P3082
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=1e4+4;
const int M=1e3+3;
int n,m,nxt[M],g[M][30],dp[N][M];
char s1[N],s2[M];
//nxt[i]:KMP中预处理模式串要用的数组
//dp[i][j]:当前文本串匹配到第i位,模式串匹配到第j位,最多保留数字的个数
//g[i][j]:当前匹配到模式串的第i位,要往后面加入字符j,此时能匹配到模式串的第几位
signed main(){
scanf("%s%s",s1+1,s2+1);
n=strlen(s1+1),m=strlen(s2+1);
int j=0;
//预处理nxt[i]
for(int i=2;i<=m;i++){
while(j&&s2[i]!=s2[j+1]){
j=nxt[j];
}
if(s2[i]==s2[j+1]) nxt[i]=j+1,j++;
}
//处理g数组
for(int i=0;i<m;i++){//i==0的状态也是合法的
for(int k=1;k<=26;k++){
if(s2[i+1]-'a'+1==k){
g[i][k]=i+1;
}
else{
g[i][k]=g[nxt[i]][k];
}
}
}
//刷表转移dp(注意我们不能用j==m的不合法状态转移其他状态)
for(int i=0;i<=n;i++){//i==0的状态也是合法的
for(int j=0;j<m;j++){
dp[i+1][j]=max(dp[i+1][j],dp[i][j]);
dp[i+1][g[j][s1[i+1]-'a'+1]]=max(dp[i+1][g[j][s1[i+1]-'a'+1]],dp[i][j]+1);
}
}
int ans=0;
//i==m的状态不合法,不能用于统计答案
for(int i=0;i<m;i++){
ans=max(ans,dp[n][i]);
}
printf("%lld",n-ans);
return 0;
}

浙公网安备 33010602011771号