彩虹岛的落叶

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]=max(dp[i-2],dp[i-3]) \]

(转移方程是正确的,但是代码我不是这么写的)。

补充: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;
}
posted @ 2023-12-02 11:43  猫猫不会摸鱼  阅读(16)  评论(0)    收藏  举报