🍕🏠🌋 当前时间是:

 

CF1714D 题解

CF1714D 题解

description

给定黑色文本 \(t\)\(n\) 个字符串 \(s_1,s_2...s_n\). 一次操作可以将 \(t\) 中与 \(s_i\) 相等的子串涂成红色。 一个位置多次涂色后仍是红色。\(s_i\) 可以使用多次。 求将 \(t\) 涂成红色的最小次数,并输出方案。 无解输出-1.

  • \(|t| \leq 100\)
  • 测试数据组数 \(\leq 100\)
  • \(n\leq 10\)
  • \(|s_i| \leq 10\)

solution

线性dp。

\(f_i\) 表示将 \(t\) 的前 \(i\) 个字符涂成红色的最小次数,则

  • \(f_0=0\)

  • \(f_i=\min\{f_j\}+1, p\leq j \leq i\)

其中 \(p\)\(i\) 减去以 \(t_i\) 结尾的能匹配的长度最大的 \(s_k\) 的长度。

特别地,若不存在这样的 \(p\),则 \(f_i=\infty\)

答案即为 \(f_{|t|}\)

为了输出方案,我们记录每个 \(f_i\) 是由哪里转移的并且当前放的是哪一个单词。

由于需要检查字符串是否在 \(\{s\}\) 中,用 std::map 匹配字符串,可将匹配复杂度从 \(\mathcal{O}(n|s_i|)\) 变为 \(\mathcal{O}(|s_i|\log n)\). 代码也更简洁。

code

#include<bits/stdc++.h>

using namespace std;

const int N=1010;

string t,s[110];
int f[N],n,pre[N],len[N];
map<string,bool> mp;
map<string,int> num;

int main(){
	
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	
	int T;
	cin>>T;
	while(T--){
		memset(pre,0,sizeof pre);
		memset(len,0,sizeof len);
		mp.clear();
		num.clear();
		f[0]=0;
		
		cin>>t>>n;
		for(int i=1; i<=n; i++) s[i].clear();
		for(int i=1; i<=n; i++) cin>>s[i];
		for(int i=1; i<=n; i++){
			reverse(s[i].begin(),s[i].end()); //倒着存字符串,方便dp的时候匹配
			mp[s[i]]=true;
			num[s[i]]=i;
		}
		
		f[t.size()+1]=INT_MAX/2; //避免+1爆int
		for(int i=1; i<=t.size(); i++){
			string now;
			f[i]=INT_MAX/2;
			int minx=t.size()+1; //记录从哪里转移
			for(int j=i; j; j--){
				now+=t[j-1];
				if(f[minx]>f[j-1]){
					minx=j-1;
				}
				if(mp.find(now)!=mp.end()){
					if(f[i]>f[minx]+1){
						f[i]=f[minx]+1;
						pre[i]=minx;
						len[i]=now.size();
					}
				}
			}
			
		}
		
		if(f[t.size()]==INT_MAX/2){
			cout<<-1<<'\n';
			continue;
		}
		
		int st=t.size();
		cout<<f[t.size()]<<'\n';
		while(st){
			string q=t.substr(st-len[st],len[st]); //此处记录了每个位置用了多长的字符串来涂色
			reverse(q.begin(),q.end());
			cout<<num[q]<<' '<<st-len[st]+1<<'\n';
			st=pre[st];
		}
		
	}
	
	return 0;
}
posted @ 2023-04-22 20:59  zzafanti  阅读(30)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end