題目連結:https://www.luogu.com.cn/problem/AT_abc343_g
solution:
1.首先我們將給出的字串中互相包含的消去,可以使用kmp求前字尾來完成。和這道題的寫法一樣https://www.luogu.com.cn/problem/CF1200E
2.我們發現給出的字串最多隻有20個,考慮狀壓來求解所有可能
3.我們注意到這道題只需要求出最小的長度即可,那麼轉移的代價就是後面拼接上去的字串的有效長度。何為有效長度:因為兩個字串相接,有可能在前一個串的字尾和後一個串的字首會出現重疊,有效長度就是後一個串的長度減去重複的這部分得到的長度。
4.開始轉移 我們設dp[state][j] state為當前的選中的字串(目前得到的母串)構成的集合,j是當前的母串以第 j 個字串結尾 ,如此得到的母串長度。
5.轉移,具體操作見程式碼。
vector<int> kmp(string s)
{
int n=s.size();
vector<int> nxt(n+1);
for(int i=1,j=0;i<n;i++)
{
while(j&&s[i]!=s[j])
{
j=nxt[j];
}
j+=(s[i]==s[j]);
nxt[i+1]=j;
}
return nxt;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
vector<string> S(n);
for(int i=0;i<n;i++)
{
cin>>S[i];
}
vector<string> tmp;
sort(all(S),[&](string a,string b){
return a.size()<b.size();
});
for(int i=0;i<n;i++)
{
int flag=1;
for(int j=i+1;j<n;j++)
{
vector<int> f=kmp(S[i]+'#'+S[j]);
if(find(all(f),S[i].size())!=f.end())
{
flag=0;
break;
}
}
if(flag)
{
tmp.push_back(S[i]);
}
}
n=tmp.size();
S=tmp;
vector cost(n,vector<int> (n));
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(i==j) continue;
vector<int> f=kmp(S[j]+'#'+S[i]);
cost[i][j]=S[j].size()-f.back();
}
}
vector dp((1<<n),vector<int> (n,1e9));
vector<string> res(1<<n);
for(int i=0;i<n;i++)
{
dp[1<<i][i]=S[i].size();
}
for(int s=1;s<(1<<n);s++)
{
for(int i=0;i<n;i++)
{
if(s>>i&1)
{
for(int j=0;j<n;j++)
{
if(!(s>>j&1))
{
dp[s|1<<j][j]=min(dp[s|1<<j][j],dp[s][i]+cost[i][j]);
}
}
}
}
}
int ans=*min_element(dp.back().begin(),dp.back().end());
cout<<ans<<'\n';
}