1 条题解
-
0
题意
给定一个字符串 ,我们需要判断是否可以通过不超过两个子序列(即从原串 中取出两个不相交的子序列)得到 。
换句话说, 可以拆分成 ,其中 是第一个子序列, 是第二个子序列。一般情况 可以为空。
思路
枚举 的所有可能长度(),然后检查对于每一对 和 是否存在解。
我们需要判断: 中是否包含 和 作为子序列,并且这两个子序列在 中的位置不相交。最初可以设计如下 DP:
设 为布尔值,表示 的前 个字符中,是否包含 的前 个字符和 的前 个字符作为不相交的子序列。
转移很简单:如果 ,我们可以跳过 ( 索引),更新 ;
如果 ,则更新 ;
如果 ,则更新 。
但这个 DP 的复杂度是 。我们可以优化:将 作为 DP 的值,而不是布尔值。
即维护 表示达到当前状态所需的最小的 。
但问题在于如何定义转移。注意到一个事实:假设当前 ,我们想向 中添加下一个字符 。
最优策略是选择 中第一个出现 的位置。
可以用反证法证明:如果第一个出现的位置是空闲的,那么取它是最优的;如果第一个出现的位置会被 占用,那么这种情况会由另一个状态 (其中 )来处理。增加 的逻辑类似。
因此,我们需要在枚举 和 之前,预处理数组 ,表示在 的后缀 中字符 下一次出现的位置。
之后每次转移时使用该数组,复杂度降为 。
算法步骤
- 枚举 的长度 (),令 ,。
- 预处理 数组,表示从 的第 个位置开始,字符 下一次出现的位置( 索引)。
- 初始化 ,其余为无穷大。
- 按顺序遍历 和 ,使用 数组进行转移:
- 如果 ,则 $dp[lena+1][lenb] = \min(dp[lena+1][lenb], nxt[dp[lena][lenb]][a[lena]] + 1)$
- 如果 ,则 $dp[lena][lenb+1] = \min(dp[lena][lenb+1], nxt[dp[lena][lenb]][b[lenb]] + 1)$
- 如果最终 ,则存在解。
- 如果所有枚举都不存在解,则输出无解。
复杂度分析
- 预处理 数组:
- 对于每个 的长度,DP 状态数为 ,转移为
- 总复杂度:,最坏情况下
代码说明
- 使用二维数组 存储最小前缀长度,初始化为一个大数(如 )。
- 使用 数组快速找到下一个字符的位置,避免线性扫描。
- 外层循环枚举 的长度,内层进行 DP 转移。
- 最终检查 是否小于等于 ,若是则输出 YES,否则继续枚举。
#include<bits/stdc++.h> using namespace std; const int MAXN = 805; int f[MAXN][MAXN]; bool check(string s,string t1,string t2){ memset(f,-1,sizeof(f)); f[0][0] = 0; s = '0' + s; t1 = '0' + t1; t2 = '0' + t2; for(int i=0; i<s.size(); i++){ for(int j=0; j<t1.size(); j++){ if(f[i][j] == -1) continue; f[i+1][j] = max(f[i+1][j],f[i][j]); if(s[i+1] == t1[j+1]) f[i+1][j+1] = max(f[i+1][j+1],f[i][j]); if(s[i+1] == t2[f[i][j] + 1]) f[i+1][j] = max(f[i+1][j],f[i][j] + 1); } } if(f[s.size()-1][t1.size()-1] == t2.size()-1) return true; return false; } bool solve(string s,string t){ for(int i=0; i<=t.size(); i++){ string t1,t2; for(int j=0; j<i; j++){ t1 = t1 + t[j]; } for(int j=i; j<t.size(); j++){ t2 = t2 + t[j]; } if(check(s,t1,t2)) return true; } return false; } int main(){ int n; cin>>n; while(n--){ string s,t; cin>>s>>t; if(solve(s,t)){ printf("YES\n"); } else{ printf("NO\n"); } } return 0; }
- 1
信息
- ID
- 6719
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者