http://acm.hdu.edu.cn/showproblem.php?pid=3828
題意:
給你N個數,求N個數在滿足下面3個條件的情況下相加的最小和。
條件一: 相加的兩個數是二進制相加;
條件二:相加的兩個數A,B , A的後綴可以和B的前綴合併成一個
條件三:相加的兩個數A,B,如果A是B的子串,則A可以不算。
思路:
很好的一道狀態壓縮dp。對於兩個數,我們可以發現,如果這兩個數都不是各自的子串,那個
這兩個數相加,要是A前B後,或者B前A後,只有這兩種情況。這樣分析了之後我們似乎就可以
推導出本題是否可以用貪心的求法,也就是說每次都是求一個值最小的那個。但是這個貪心思路
是不正確的,那麼我們就可以考慮dp,但是線性的dp是不滿足最優子性質的, 那麼我們就可以用
狀態壓縮來記錄當前的狀態。因此本題的做法是這樣的:用dp[i][j]表示狀態爲j,以i爲前綴的最小
長度,求出最小長度之後通過構造最小串就可以得出問題的解了。
#include<string>
#include<iostream>
#include<algorithm>
#include<string.h>
#include <cstdio>
#define MIN(a,b) ( (a)>(b)?(b):(a) )
using namespace std;
typedef __int64 LL ;
const LL Mod = 1000000009 ;
int n , N;
const int MM = 16 ;
string word[MM] ;
int ll[MM][MM] ;
int dp[MM][1<<16] ;
string ans ;
void init(){
LL a ;
for(int i=0;i<n;i++){
cin >> a ;
word[i] = "" ;
while( a ){
if( a&1 ) word[i] += '1' ;
else word[i] += '0' ;
a >>= 1 ;
}
reverse( word[i].begin() , word[i].end() );
}
}
void move(){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if( i!=j && word[j].find( word[i] ) != string::npos ){
word[i--] = word[ -- n ] ;
break ;
}
}
}
}
void deal1(){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int&lap = ll[i][j] = word[i].size() ;
while( word[i].substr(word[i].size() - lap) != word[j].substr(0 , lap) )
lap -- ;
}
}
}
int DP(int i , int j){
if( dp[i][j] != -1 ) return dp[i][j] ;
int jj = j ^ ( 1<<i ) ;
dp[i][j] = jj ? (1<<30) : word[i].size() ;
for(int ii=0;ii<n;ii++){
if( (jj&(1<<ii)) != 0 ){
dp[i][j] = MIN( dp[i][j] , DP(ii,jj) + word[i].size() - ll[i][ii] );
}
}
return dp[i][j] ;
}
void deal2(){
N = 1<<n ;
memset(dp , -1, sizeof(dp));
for(int i=0;i<n;i++){
DP( i , N-1 );
}
//DP();
int m = N - 1 ;
ans = "" ;
int prev = -1 ;
while( m ){
int best = -1;
int bestLen;
string bestAdd;
for (int f = 0; f < n; f++) if (m & (1 << f) ){
int len = dp[f][m];
string add = word[f];
if (prev >= 0) {
len -= ll[prev][f];
add = add.substr(ll[prev][f]);
}
if (best < 0 || len < bestLen || len == bestLen && add < bestAdd) {
best = f;
bestLen = len;
bestAdd = add;
}
}
ans += bestAdd;
prev = best;
m ^= ( 1 << best ) ;
}
int cnt = ans.size() ;
LL res = 0 ,add = 1 ;
for(int i=cnt-1;i>=0;i--){
if( ans[i] == '1' ) res = ( res + add ) % Mod ;
add = add * 2 % Mod ;
}
cout << res << endl ;
}
int main(){
while( cin >> n ){
init() ;
move() ;
deal1() ;
deal2() ;
}
return 0 ;
}