先说一下我了解的压缩状态dp吧。
经典的压缩dp问题是TSP问题: 一个n个点的带权的有向图,求一条路径,使得这条路经过每个点恰好一次,并且路径上边的权值和最小(或者最大)。或者求一条具有这样性质的回路。
压缩dp问题中存储点集是利用二进制形式,比如 3 = 0000,0000,0000,0011 表示第一个和第二个点在点集中,5 = 0000,0000,0000,0101 表示第一个和第三个点在点集中,以此类推。
呃,简单来说,就是用一个数表示一种状态,一般n<= 16 或n <= 32 。
状态转移:如果状态存在,dp[i][j] = 0 ,否则为无穷大.
1、状态存在:dp[i][j] = min(dp[k][s] + v[s][j] );k表示i集合中去掉了j点的集合,s遍历集合k中的点并且dp[k][s]状态存在,点s到点j有边存在,v[s][j]表示边的权值。
2、状态不存在,则为无穷大。 最后的结果是: min( dp[( 1<<n ) – 1][j] ) ( 0 <= j < n );
利用二进制还有一个好处,就是对集合的一些操作可以用位运算来实现, 例如从集合i中去掉点j: k = i & (~( 1<<j)) 或者 k = i - (1<<j)
好吧,现在来说说这题;
题意:给出N个任意长度的字符串,一些字符串的首尾有些字符时相同的,可以相互重叠起来缩短长度,让你求把这N个字符串连起来的最短的长度。
思路:听说这题可以暴力过,不过没试过,可以求出两个字符串的最长重叠长度,然后全排列。还有好多人用搜索过的,不过我是用的压缩dp,先预处理一下字符串,求出任意两个字符串相连的最大重叠长度,然后用一下压缩dp救过了。
代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> #include <algorithm> #include <math.h> #define N 24 #define M 12 using namespace std ; int att[N][N] , len[M] , dp[1<<M][N] ; char str[M][N] ; int n ; void cal ( int x , int y ) { int i , j , k ; int len1 = len[x] ; int len2 = len[y] ; int lenx = len1 < len2 ? len1 : len2 ; for ( i = lenx - 1 ; i >= 0 ; i-- ) { for ( j = len1 - i - 1 , k = 0 ; j < len1 ; j++ , k++ ) if ( str[x][j] != str[y][k] ) break; if ( k == i + 1 ) { att[x][y] = k ; return ; break; } } att[x][y] = 0 ; } int main() { int cas , i , j , k ; scanf( "%d" , &cas ); while( cas-- ) { scanf( "%d" , &n ); getchar(); for( i = 0 ; i < n ; i++ ) { scanf( "%s" , str[i] ); getchar(); //求出每个字符串长度 len[i] = strlen( str[i] ); } //预处理,算出任意两个字符串重叠的最大长度 for ( i = 0 ; i < n ; i++ ) { for ( j = 0 ; j < n ; j++ ) if ( i != j ) cal ( i , j ); } memset( dp , -1 , sizeof( dp )); //压缩状态dp for ( i = 0 ; i < n ; i++ ) dp[1<<i][i] = len[i] ; int np = ( 1<<n ) - 1 ; for ( i = 1 ; i <= np ; i++ ) { for( j = 0 ; j < n ; j++ ) { if ( dp[i][j] == -1 || ( i&( 1<<j )) == 0 ) continue ; for ( k = 0 ; k < n ; k++ ) { if ( j == k ) continue ; if ( i&( 1<<k )) continue ; int tem = i|(1<<k); if ( dp[tem][k] == -1 || dp[i][j] - att[j][k] + len[k] < dp[tem][k] ) dp[tem][k] = dp[i][j] - att[j][k] + len[k] ; } } } int minx = -1 ; for ( i = 0 ; i < n ; i++ ) if( minx == -1 || dp[np][i] < minx ) minx = dp[np][i] ; printf( "%d\n" , minx ); } return 0 ; }