题目:Husam and the Broken Present 2
思路:
被其他串包含的串一定不会对答案造成影响,所以先把这些串去掉。
对于其他的串,可以预处理出两两之间的公共部分的长度,即第一个串的前缀和第二个串的后缀有多少公共部分。
令状态f[i][j]表示最后链接的是i串,其中j的串已被使用的最优结果。j是所有串是否被使用的01数组的二进制表示。
状态转移方程:
f[k][j|x]=min(f[k][j|x],f[i][j]+m[k]-g[i][k])
边界:
f[k][(1<<k-1)]=m[k]
代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 15
#define maxm 100
#define maxs (1<<16)
#define inf (1<<30)
int n;
int m[maxn+5]= {0};
vector<int> a[maxn+5];
int g[maxn+5][maxn+5]= {0};
int f[maxn+5][maxs+5]= {0};
void initf(int St) {
for(int j=1; j<St; j++) {
for(int i=1; i<=n; i++) {
f[i][j]=(1<<30);
}
}
for(int k=1; k<=n; k++) {
f[k][(1<<k-1)]=m[k];
}
}
int dp() {
int St=(1<<n);
initf(St);
for(int j=1; j<St; j++) {
for(int i=1; i<=n; i++) {
if(!((1<<i-1)&j)) continue;
for(int k=1; k<=n; k++) {
int x=(1<<k-1);
if(!(j&x)) {
f[k][j|x]=min(f[k][j|x],f[i][j]+m[k]-g[i][k]);
}
}
}
}
int ans=inf;
for(int i=1; i<=n; i++) {
ans=min(f[i][(1<<n)-1],ans);
}
return ans;
}
void readin() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&m[i]);
for(int j=1; j<=m[i]; j++) {
int x;
scanf("%d",&x);
a[i].push_back(x);
}
}
}
int findprefix(int y,int x) {
int ans=0;
for(int i=0; i<m[x]; i++) {
int s=0;
for(int j=m[y]-i-1;j<m[y];j++) {
if(a[x][s]!=a[y][j]) goto T;
s++;
}
ans=max(ans,s);
T:;
}
return ans;
}
bool cnt[maxn+5]={0};
bool findh(int x,int y) {
for(int i=0;i<m[y]-m[x];i++) {
for(int j=0;j<m[x];j++){
if(a[x][j]!=a[y][i+j]) goto F;
}
return true;
F:;
}
return false;
}
void del(){
for(int i=1;i<=n;i++) {
if(!cnt[i]) continue;
for(int j=i;j<=n;j++) {
a[j]=a[j+1];
cnt[j]=cnt[j+1];
m[j]=m[j+1];
}
n--,i--;
}
}
void init() {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(i==j||cnt[i]||cnt[j]) continue;
if(m[i]>m[j]) continue;
if(findh(i,j)) cnt[i]=1;
}
}
del();
for(int i=1; i<=n; i++) {
for(int j=1; j<=n; j++) {
if(j==i) continue;
else g[i][j]=findprefix(i,j);
}
}
}
int main() {
readin();
init();
int ans=dp();
printf("%d",ans);
return 0;
}