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;
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号