题目
一个环,给定n(n<=16)个字符串,都是环上的字串,求原始的环最小是多长
分析
这道题感觉挺麻烦的,关键是给定字符串的顺序可以是顺时针的,也可以是逆时针,然后还要确定起始位置,最后连成环的时候(就是首尾相接时),如何保证环是最小的
开始时我理解错了,认为给了BGGB和BGG拼的是出来的BGG 而不是BGGBGG,读了代码后确定了所有的字符串都是环的子串,不存在循环的可能
首先预处理,将是其他字符串子串的字符串删除,这里有很多小技巧,感觉很简洁,可以参考刘汝佳的代码。
然后因为是个环,将[0][0] 第0字符串正序作为起始 dp[1][0][0]=len[0],用刷表法更新状态
搜索结尾的字符串,确定结果
首先将字符串拼接成一条线
状态 dp[s][j][k] s是一个集合,代表已经被连接的字符串集合,j是连接的最后一个字符串,k=0 or 1代表j的顺序,dp[s][j][k]代表s个字符串重叠起来长度
最后将j(j需要比较才能确定)和0相连,减去覆盖的部分,就可以连接成环
状态转移方程:对于现有的状态s,接尾是字符串j,顺序k,可以向他右边继续添加字符串i,i的顺序是l,转移方程如下
最后,如果结果是1,要输出2,因为题目要求
总结
还是对于状压dp不够熟悉,这题使用状压dp还是很明显的,问题是怎么表示字符串顺序(增加一个维度表示最后一个字符串的顺序),本题中运用了一些技巧简化计算,值得注意
//
// Created by Zhao Pengfei on 2019/11/14.
//
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <utility>
using namespace std;
const int maxn=16;
struct Item{
string str,rev;
bool operator < (const Item &rhs) const {
return str.length()<rhs.str.length();
}
};
//overlap[i][j][k][l]中,i,j代表两个字符串的序号,k代表i的顺序,l代表j的顺序
//dp[s][j][k]
int n,m,overlap[maxn][maxn][2][2],dp[1<<16][maxn][2],len[maxn];
Item items[maxn];
string s[maxn][2]; //小技巧,这样分成二维表示顺序,方便传入Overlap函数中计算,不用写if判断顺序
//a(left),b(right) overlap length
int Overlap(string &a,string &b){
int len1=a.length();
int len2=b.length();
for(int i=0;i<len1;++i){
if(i+len2<=len1) continue; //没有到能够重合的位置
bool flag=true;
for(int j=0;i+j<len1;++j){
if(a[i+j]!=b[j]){
flag=false;
break;
}
}
if(flag){
return len1-i;
}
}
return 0;
}
void Init(){
//输入,预处理,删除被包含的子字符串
for(int i=0;i<n;++i){
cin>>items[i].str;
items[i].rev=items[i].str;
reverse(items[i].rev.begin(),items[i].rev.end());
}
sort(items,items+n);
m=0;
for(int i=0;i<n;++i){
bool flag=true;
for(int j=i+1;j<n;++j){
if(items[j].str.find(items[i].str)!=string::npos
||items[j].str.find(items[i].rev)!=string::npos ) {
flag = false;
break;
}
}
if(flag){
s[m][0]=items[i].str;
s[m][1]=items[i].rev;
len[m]=s[m][0].length();
m++;
}
}
for(int i=0;i<m;++i){
for(int j=0;j<m;++j){
for(int k=0;k<2;++k){
for(int l=0;l<2;++l){
overlap[i][j][k][l]=Overlap(s[i][k],s[j][l]);
}
}
}
}
}
void update(int &x,int y){
if(x==-1 || y<x) x=y;
}
//这里使用刷表法进行状态转移
void Solve(){
memset(dp,-1,(1<<m)*sizeof(dp[0]));
dp[1][0][0]=len[0];
int S=(1<<m);
//因为是刷表法,s可以小于S-1
for(int s=0;s<S-1;++s){
for(int j=0;j<m;++j){
for(int k=0;k<2;++k){
if(dp[s][j][k]<0) continue;
int t=dp[s][j][k];
for(int i=0;i<m;++i){ //选择下一个字符串
if(s&(1<<i)) continue;
update(dp[s|(1<<i)][i][0],t+len[i]-overlap[j][i][k][0]);
update(dp[s|(1<<i)][i][1],t+len[i]-overlap[j][i][k][1]);
}
}
}
}
int ans=-1;
for(int i=0;i<m;++i){
for(int k=0;k<2;++k){
if(dp[S-1][i][k]==-1) continue;
update(ans,dp[S-1][i][k]-overlap[i][0][k][0]);
}
}
if(ans<=2) printf("2\n");
else printf("%d\n",ans);
}
int main(void){
while(cin>>n && n){
Init();
Solve();
}
}