LGP4590 [TJTS 2018] 游园会 学习笔记

LGP4590 [TJTS 2018] 游园会 学习笔记

Luogu Link

前言

本文“LCS”均指最长公共子序列。

题意简述

给定一个长为 \(m\) 的字符串 \(S\)。对于从 \(0\)\(m\) 的每一个整数 \(i\),求有多少长为 \(n\) 的字符串 \(T\),满足 \(|\text{lcs}(S,T)|=i\),且 \(T\) 中不存在子串 \(\texttt{NOI}\)

\(m\le 15,n\le 10^3\)。本题中出现的所有字符串的字符集均为 \(\{\texttt{N},\texttt{O},\texttt{I}\}\)

时间限制 \(\text{6.00s}\)

做法解析

这是个对满足某限制的字符串进行计数的东西,我们往DP和自动机之类的方面想。

\(f_{i,\dots}\) 为当前考虑到长度为 \(i\) 的串,满足某些限制下的串数,我们每次转移枚举当前字符串结尾填哪个字符,这是DP的框架。

我们肯定还要记一维 \(j\) 处理最长公共子序列的限制,至于不含子串 \(\texttt{NOI}\) 则很容易处理,我们记一维 \(k\in \{0,1,2\}\) 表示这个字符串末尾匹配 \(\texttt{NOI}\) 的进度就行。

问题来了,\(j\) 这一维具体怎么搞?令 \(j\) 表示 \(|\text{lcs}(S,T)|\)?不好意思,当新加进来一个字符时你无法判断这一位能否进一步匹配,因为你不知道上一次匹配到哪了。

然而 \(m\le 15\)。这明示我们状压。状压了就好办了。怎么个状压法?

设我们状压状态的第 \(j\) 位对应 \(S\) 的第 \(j+1\) 个字符。一种最直接的想法是令第 \(j\) 位为 \(1\) 表示 \(S_j\) 处在当前的LCS中,反之亦然。这么做的问题是有时LCS并不唯一。举个例子:

\(S[1,3]=\texttt{ONI}\)\(T[1,3]=\texttt{OIN}\)

显然此时其LCS并不唯一,我们显然不能把三位都涂上 \(1\),怎么办?一个想法是我们钦定当有多个LCS时涂“最靠前”(优先考虑第一位,在此基础上考虑第二位,依此类推)的那个,另一个想法是我们存差分数组(对于同一个 \(S\),随着匹配长度增加,其LCS长度是单增的,而且每次新增进一个字符参与匹配,LCS长度最多增加 \(1\))。实际上手玩一下可以发现这两个想法实际上是一个东西。

可以证明这么做满足无后效性。粗略地理解,我们往 \(T\) 后面加字符可能可以发掘出 \(S\) 有可以更好匹配的地方,但是我们无论何时都考虑的是整个串 \(S\),所以说一个状态就意味着满足这个状态的 \(T\) 已有的部分不可能再有任何用处了(即使这些部分可以凑成别的长度相同的LCS,也没有必要考虑,因为等长的LCS在后续只加 \(T\) 的转移中的表现是相同的),相当于空气,满足这个状态的 \(T\) 就可以视作当前的LCS去考虑后续转移。

啊,满足无后效性我们就放心了!

那我们最终的DP状物就出来了:设 \(f_{i,j,k}\) 为考虑到 \(T\) 的第 \(i\) 位,当前和 \(S\) 的LCS被状压的结果为 \(j\)\(\texttt{NOI}\) 的组合进度为 \(k\) 的方案数。我们枚举下一位填哪个字母即可转移。时间复杂度 \(O(n2^m)\)

我们设所有可达状态构成的集合为 \(\Sigma\)(这是大写希腊字母西格玛,不是求和符号)。实际上,有用状态数量 \(|\Sigma|\) 大概率是不能跑满 \(2^m\) 的。如果我们提前设计一个记忆化搜索把 \(j\) 这一维的转移关系都搜出来,我们会发现其只有 \(2^m\)\(0<x<1\) 倍。在这题里面,搜出来的可达状态经粗略测试不会超过 \(6\times 10^3\) 种。

这下我们主循环里面 \(j\) 这一维的枚举就从 \(2^m\) 优化到 \(|\Sigma|\) 了,可喜可贺!总时间复杂度 \(O(n|\Sigma|)\),下文中的代码总用时 \(\text{157ms}\),完全用不着六秒的时限呐!

代码实现

主DP采用刷表法转移,那个循环里面八个转移从上到下依次对应:

  1. \(\texttt{X+N}\),匹配进度从 \(0\) 来到 \(1\)(其中 \(\texttt{X}\) 不为 \(\texttt{N}\)\(\texttt{NO}\))。
  2. \(\texttt{X+O}\),仍然没有匹配进度。
  3. \(\texttt{X+I}\),仍然没有匹配进度。
  4. \(\texttt{N+N}\),匹配进度仍为 \(1\)
  5. \(\texttt{N+O}\),匹配进度从 \(1\) 来到 \(2\)
  6. \(\texttt{N+I}\),匹配进度归零。
  7. \(\texttt{NO+N}\),匹配进度回到 \(1\)
  8. \(\texttt{NO+O}\),匹配进度归零。
  9. 为什么没有 \(\texttt{NO+I}\) 啊,好难猜啊。

可以看到 nxt 数组中的第二维正对应我们新加的字符。

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace omodint;
using mint=m107;
const int MaxM=20,BipM=(1<<15),MaxN=1e3+5,MaxS=6e3+5;
int N,M;char S[MaxM];
int tot,mp[BipM],nxt[MaxS][3],len[MaxS],g[2][MaxM];
mint dp[MaxN][MaxS][3],ans[MaxM];
int dfs(int u){
    if(mp[u])return mp[u];mp[u]=++tot;
    auto work=[&](int c,char ch)->void {
        for(int i=1;i<=M;i++)g[0][i]=g[0][i-1]+((u>>(i-1))&1);
        len[mp[u]]=g[0][M];int v=0;
        for(int i=1;i<=M;i++){
            g[1][i]=max(g[0][i],g[1][i-1]);
            if(S[i]==ch)maxxer(g[1][i],g[0][i-1]+1);
            v|=((g[1][i]-g[1][i-1])<<(i-1));
        }
        nxt[mp[u]][c]=dfs(v);
    };
    work(0,'N'),work(1,'O'),work(2,'I');
    return mp[u];
}
int main(){
    readis(N,M),scanf("%s",S+1);
    dfs(0);dp[0][mp[0]][0]=1;
    for(int i=0,ci=0;i<N;i++,ci=i&1){
        for(int j=1;j<=tot;j++){
            if(dp[ci][j][0]!=0){
                dp[ci^1][nxt[j][0]][1]+=dp[ci][j][0];
                dp[ci^1][nxt[j][1]][0]+=dp[ci][j][0];
                dp[ci^1][nxt[j][2]][0]+=dp[ci][j][0];
            }
            if(dp[ci][j][1]!=0){
                dp[ci^1][nxt[j][0]][1]+=dp[ci][j][1];
                dp[ci^1][nxt[j][1]][2]+=dp[ci][j][1];
                dp[ci^1][nxt[j][2]][0]+=dp[ci][j][1];
            }
            if(dp[ci][j][2]!=0){
                dp[ci^1][nxt[j][0]][1]+=dp[ci][j][2];
                dp[ci^1][nxt[j][1]][0]+=dp[ci][j][2];
            }
            for(int k=0;k<=2;k++)dp[ci][j][k]=0;
        }
    }
    for(int j=1;j<=tot;j++)for(int k=0;k<=2;k++)ans[len[j]]+=dp[N&1][j][k];
    for(int i=0;i<=M;i++)writil(miti(ans[i]));
    return 0;
}

后记

写题解的好处在于它逼着你把题目讲清楚,要讲清楚就要你自己理解透彻。

你看别人的题解里面有些地方其实没证明,别人可能觉得显然,但是你自己真的搞懂了吗?还是说蒙混过去了?

动态规划和贪心的正确性不想明白,自己心里面真的能安心么?

望诸君共勉。

posted @ 2025-05-03 20:54  矞龙OrinLoong  阅读(5)  评论(0)    收藏  举报