Atcoder ABC329E Stamp 题解 [ 绿 ] [ 线性 dp ]

Stamp:难点主要在 dp 转移的细节与分讨上,但通过改变状态设计可以大大简化分讨细节的题。

观察

首先要有一个观察:只要某一个前缀能被覆盖出来,那么无论它后面多出来多少,后面的字符串都可以帮他重新覆盖回去。这也就是 dp 为啥没有后效性的原因。

除此之外,还要注意一个字符串不仅能在其他字符串上面,还能被盖在最下层来达到用子串接着覆盖的效果。

暴力 dp 思路

\(dp_i\) 表示第 \(i\) 个字符的前缀能否被覆盖。每次转移的时候枚举当前位和 \(B\) 的第几个字符匹配,然后判断某一段是否相等,进行转移即可。这个做法感觉比较难写,但肯定没有假。时间复杂度 \(O(nm^2)\),显然可过。

更巧妙的 dp 思路

\(dp_{i,j}\) 表示 \(A\) 匹配到第 \(i\) 个,\(B\) 匹配到第 \(j\) 个是否可行。

那么接下来分为三个情况,这三种情况的前提条件就是 \(A_i=B_j\),否则一定无解:

  • \(j=1\),也就是当前 \(B\) 刚开始匹配。也就是说前面的部分只要能被覆盖出就行了,不管他前面匹配到多少个,则 \(dp_{i,j}\gets dp_{i,j}\bigvee_{k=1}^{m}dp_{i-1,k}\)
  • 这个字符接着上一个字符覆盖,则 \(dp_{i,j}\gets dp_{i,j}\bigvee dp_{i-1,j-1}\)
  • 这个字符被盖在最下面,用自己的后缀覆盖。这种情况需要满足的条件是前一个字符串一定要先被覆盖完,才可以在下一个字符串覆盖前覆盖在它的下面。则 \(dp_{i,j}\gets dp_{i,j}\bigvee dp_{i-1,m}\)

直接这样转移复杂度也是 \(O(nm^2)\) 的,但是第一种情况只要记录前一位是否含 \(1\) 即可优化为 \(O(nm)\)。总体时间复杂度 \(O(nm)\)

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
bool dp[200005][10],hv[200005];
int n,m;
char a[200005],b[10];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>a+1>>b+1;
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(a[i]==b[j])
            {
                dp[i][j]|=dp[i-1][j-1];
                dp[i][j]|=dp[i-1][m];
                if(j==1)dp[i][j]|=hv[i-1];
            }
            hv[i]|=dp[i][j];
        }
    }
    if(dp[n][m])cout<<"Yes";
    else cout<<"No";
    return 0;
}
posted @ 2025-01-09 23:20  KS_Fszha  阅读(1)  评论(0编辑  收藏  举报