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;
}
标签:pre,int,题解,len,minx,leq,mp,CF1714D
From: https://www.cnblogs.com/FreshOrange/p/17343939.html