[JZOJ3105]拼图
题目大意:
给你一个起始串$a(|a|\leq 300)$,一个目标串$b(|b|\leq 300)$,以及$n(n\leq 8)$个小串$s_0,s_2,\ldots,s_{n-1}(|s_i|\leq 400)$,你可以进行若干次操作将$a$变成$b$。
操作的规则如下:
1.取出每个小串的任一后缀,代价为去除后缀的长度;
2.移除起始串中的若干字符,每移除一个字符需要$1$的代价。
3.选择一些小串并将其插入到$a$中(包括左边界,不包括右边界);
问将$a$变成$b$的最小代价。
思路:
状压DP。
用$f_{i,j}=k$表示选取小串的状态为$i$,目标串匹配长度为$j$时,起始串$a$匹配的最少长度为$k$。
对于状态$f_{i,j}$转移时分两种情况,一种是用起始串的一个字母匹配,另一种是用小串来匹配。
用起始串匹配时,找到$a$中下一个和$b_{j+1}$相同的字符$a_k$,向$f_{i,j+1}$转移。
用小串匹配时,枚举没有选过的小串$s_k$找到能和目标串匹配的最长前缀长度$l$,向$f_{i+2^k,j+l}$转移。
最后统计答案时,枚举最后小串选取的状态,答案即为起始串长度+选取的小串长度-目标串长度。
边界情况:$f_{i,j}\geq|a|$,这时再转移就到了$a$的右边界,因此不予转移。
1 #include<cstdio> 2 #include<cctype> 3 #include<cstring> 4 #include<algorithm> 5 inline int getint() { 6 register char ch; 7 while(!isdigit(ch=getchar())); 8 register int x=ch^'0'; 9 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 10 return x; 11 } 12 const unsigned inf=~0u; 13 const int LEN1=302,LEN2=402,N=8; 14 char s1[LEN1],s2[LEN1],s[N][LEN2]; 15 unsigned f[1<<N][LEN1],len1,len2,len3[N]; 16 int main() { 17 scanf("%s%s",&s1[1],&s2[1]); 18 len1=strlen(&s1[1]),len2=strlen(&s2[1]); 19 const int n=getint(); 20 for(register int i=0;i<n;i++) { 21 scanf("%s",&s[i][1]); 22 len3[i]=strlen(&s[i][1]); 23 } 24 memset(f,0xff,sizeof f); 25 f[0][0]=0; 26 for(register int i=0;i<1<<n;i++) { 27 for(register int j=0;j<(int)len2;j++) { 28 if(f[i][j]>=len1) continue; 29 for(register unsigned k=f[i][j]+1;s1[k];k++) { 30 if(s1[k]==s2[j+1]) { 31 f[i][j+1]=std::min(f[i][j+1],k); 32 break; 33 } 34 } 35 for(register int k=0;k<n;k++) { 36 if(i&(1<<k)) continue; 37 for(register int l=1;s[k][l]&&j+l<=(int)len2&&s[k][l]==s2[j+l];l++) { 38 f[i|(1<<k)][j+l]=std::min(f[i|(1<<k)][j+l],f[i][j]); 39 } 40 } 41 } 42 } 43 unsigned ans=inf; 44 for(register int i=0;i<1<<n;i++) { 45 if(f[i][len2]==inf) continue; 46 unsigned tmp=0; 47 for(register int j=0;j<n;j++) { 48 if(i&(1<<j)) tmp+=len3[j]; 49 } 50 ans=std::min(ans,len1+tmp-len2); 51 } 52 printf("%u\n",ans); 53 return 0; 54 }