彩虹岛的落叶
I-彩虹岛的落叶
题目背景
立冬一过,树上的叶片纷纷散落在了彩虹岛的大路上。\(Ran\) 一个人走在街上无所事,闲下来观察起了这些叶片。
\(Ran\) 发现这些叶片十分的相似,他可以把每个叶片的样子都写成一串仅由大写字母组成的字符串,然后挑选分析了 \(n\) 个叶片后,他得出结论,他认为这些其他所有的叶片都可以从上面这 \(n\) 个叶片里找到原型,且每个原型的字符串长度必须大于等于 \(2\)。\(Ran\) 还想复习期末考试的内容呢, 所以他想请求你帮帮他验证他的结论是否正确。但由于手工验证太头疼了,还想复习期末考试的内容呢,所以他想请求你帮帮他验证他的结论是否正确。
题目描述
给出 \(n\) 个长度全是大写字母(A~Z)组成的字符串叶片 \(s_1-s_n\) ,和所有叶片最大字符串的大小 \(m\),然后有需要求证的 \(q\) 个叶片 \(q_1-q_n\) ,问你是否能将 \(t_i\) 分成若各个长度不小于 \(2\) 的连续字段,目这些连续字段都能在他分析过的 \(n\) 个叶片
可以匹配上。
比如有两片树叶 ABCD,ACBA 要求证 ABCBA,则可以划分为AB CBA,AB在第一片树叶中出现过,CBA在第二片树叶中出现过。
如果有输出“YES”,无解则输出“NO”
输入格式
第一行有两个数 \(n\),\(m\)
接下来有 \(n\) 行,每行一个字符串分别为\(s_1-s_n\)
第 \(n+2\) 行有一个数 \(q\)
接下来有 \(q\) 行,每行一个字符串分别为 \(t_1-t_n\)
输出格式
一共 \(q\) 行,每行输出 \("YES"\) 或者 \("NO"\)
输入样例
7 8
AABBCC
ABCA
CC
AABBCC
ABC
CC
AABBCC
4
AABBCCCC
DAB
ABCBCA
CCC
输出样例
YES
NO
YES
NO
数据范围
对于 \(100\%\) 数据:$2\leq n \leq 2 \times 10^3,1 \leq m \leq 2 \times 10^3,1 \leq q \leq 100 $
数据保证一组数据中的 \(n \times m \times q \leq 5 \times 10^6\)
我的思路
In fact,这题拿到手的时候我没看懂,还跑去问了几个学长这题题面是啥意思。
在学长的帮助下,我终于理解了题目:
给 \(n\) 个字符串(我们称作原型串),再给 \(q\) 个字符串,你需要拆解这 q 个字符串(我们称作匹配串)为不小于 \(2\) 的串,使得拆解出来的内容能够与任意一个原型串中的某一段匹配。如果不存在这样的拆法,输出“\(NO\)”即可。
例如:acbda dcba为原型串,现在正在匹配的串为acba,将匹配串拆解成 ac ba,ac在原型串acbda出现过,ba在daba出现过,所以acba是合法的。
现在有个原型串 abcab 和一个匹配串 abcab,一眼看过去,很自然就知道不需要拆解它们也相等,可是实际处理上,我们并不知道原型串和匹配串能匹配的长度到底有多长。既然不知道要拆成多长,那我们不妨把匹配串拆成最小单元,因为如果一个长度足够长的串能够成功找到原型,那么将它分解得到的子串也一定能够找到原型。如果是长度为奇数的串,那么让任意一个串长度为 \(3\) 就好了。可以证明,若干个 \(2\) 与若干个 \(3\) 的任意组合可以表示所有出现的情况。
基于上述原理,我们需要把原型串拆成长度为 \(2\) 和 \(3\) 的串并进行哈希存储,方便接下来的寻找匹配。
现在我们已经做完了原型串的拆解工作,那么如何拆解匹配串才能够让它与原型串很好的匹配呢?
我们很容易就会想到搜索:从当前位置向后选取长度为 \(2\) 或 长度为 \(3\) 的串,如果匹配,则继续向下搜索,如果不匹配或长度不够,则立即返回。
理论匹配时间取决于字符串长度,假设字符串长度为 \(len\),一次匹配时间最坏约为 \(O(2^{len})\)(但我认为应该存在很厉害的剪枝能够剪到 \(O(len)\))
搜索耗时太长,我们换一种方法。
如果动态规划入过门的话,很容易就能想到动态规划。(我动态规划已经入土了,所以想不到)
首先分解问题,我们将问题转化为:前 \(len\) 个字符是否成功匹配,然后不断缩小问题,最终缩小到:前 \(2\) 个字符是否成功匹配。(最小分解长度为 \(2\))
接下来,是整道题的精髓——dp数组的设计。
对于这题,我们只关心当前位能不能从第 \(i-2\) 位转移来或者从 \(i-3\) 位转移来,如果可以转移,dp 数组则记录当前位可以扩展,否则不能扩展。
因此,dp 数组的含义为:dp[i]:表示第 \(i\) 位是否能扩展。
我们约定,能够扩展为 \(1\),否则为 \(0\)。
由上,我们同时可以确定状态转移方程:
(转移方程是正确的,但是代码我不是这么写的)。
补充:dp[i] 能够为 \(1\) 的条件有两个:
-
\(dp[i-2]=1\) 且 \(substr(i-2,2)\) 能与原型串匹配
-
\(dp[i-3]=1\) 且 \(substr(i-3,3)\) 能与原型串匹配
cf 有原题来着
代码实现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=3e6+10;
const ll inf = 1e9+7;
inline ll read(){
ll s=0,w=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
return s*w;
}
int nxt[N],dp[N],len,cnt,n,m,k;
map<string,int> table;
string s;
void change() {
ll len=s.length();
for(int i=0;i<len-1;++i) {
if(i+1<len) {
string str=s.substr(i,2);
if(!table[str]) table[str]=1;
}
if(i+2<len) {
string str=s.substr(i,3);
if(!table[str]) table[str]=1;
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("q.in","r",stdin);
freopen("q.out","w",stdout);
#endif
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;++i) {
cin>>s;
change();
}
cin>>k;
while(k--){
cin>>s; int len=s.size(); s=' '+s;
//cout<<s<<"\n";
for(int i=1;i<=m;++i) dp[i]=0;
dp[0]=1;
for(int i=1;i<len;++i) {
if(i+1<=len) {
string str=s.substr(i,2);
if(table[str]&&dp[i-1]) dp[i+1]=1;
}
if(i+2<=len) {
string str=s.substr(i,3);
if(table[str]&&dp[i-1]) dp[i+2]=1;
}
}
if(dp[len]) cout<<"YES\n";
else cout<<"NO\n";
}
return 0;
}

浙公网安备 33010602011771号