Luogu P8112 [Cnoi2021] 符文破译 题解 [ 蓝 ] [ KMP ] [ 线性 dp ] [ 决策单调性 dp ]

符文破译:KMP + dp 的好题。

暴力 dp

不难打出一个暴力 dp:设计 \(dp_i\) 表示当前前 \(i\) 位全部完成了匹配,所需的最小分割数。

转移也是简单的,我们在 KMP 的过程中进行 dp 转移,每次选取 next 不断跳向再前面的 next,然后进行转移即可。

很显然一个字符集大小为 \(1\) 的串就能轻松卡掉这个,因为 KMP 的复杂度是基于均摊的,这个不满足均摊性质。时间是 \(O(n^2)\) 的。

决策单调性

我们观察每个 dp 值是如何做决策的,先是走到最长的匹配位置,然后匹配的长度不断减小。

同时,根据贪心思想,前面的 dp 值一定不比后面的 dp 值大。为啥呢,可以用反证法。假设前面的 dp 值比后面的 dp 值大,那么分割数必定比后面的多。如果后面的都能分割,又因为每一段都是模式串的前缀,所以一个成功分割的字符串无论你从后面删多少个数它都是合法的分割,这样前面的 dp 值一定能从后面的 dp 值的分割方案中求出。因此前面的 dp 值一定不大于后面的。

于是只要转移最长的 next 就可以了。

其他的注意一下无解判断即可。

时间复杂度 \(O(n)\)

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
int n,m,dp[10000005],ne[10000005];
char s[10000005],t[10000005];
void solve()
{
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    cin>>n>>m>>s+1>>t+1;
    for(int i=2,j=0;i<=n;i++)
    {
        while(j&&s[j+1]!=s[i])j=ne[j];
        if(s[j+1]==s[i])j++;
        ne[i]=j;
    }
    for(int i=1,j=0;i<=m;i++)
    {
        while(j&&s[j+1]!=t[i])j=ne[j];
        if(s[j+1]==t[i])j++;
        if(dp[i-j]!=-1)dp[i]=dp[i-j]+1;
    }
    if(dp[m]!=-1)cout<<dp[m];
    else cout<<"Fake";
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    solve();
    return 0;
}
posted @ 2024-12-20 22:54  KS_Fszha  阅读(31)  评论(0)    收藏  举报