题目链接:
题目大意:
给出n个长度为m的字符串,要求每个字符串必须有一位是区别于其他字符串的,我们修改某个字符串的某一位有一个固定的花费,问修改成符合要求的字符串的最小花费。
题目分析:
- 我们定义dp[i][state]表示前i字符串达到state状态时的最小花费。
- 对于一个不符合要求的字符串,我们可以通过修改它的某一位字符达到符合要求,因为字符串不超过小写字母的个数。
- 可以通过当前要转换为合法状态字符串的某一位,或者直接修改与它在这一位具有相同字符的除去最大的字符串的其他串,顺便将这一组字符串变为合法。
- 修改第i个字符串的第j个字符时,满足转移方程:
dp[i][state|(1<<j)]=min{dp[i−1][state]+cost[i][j]}
- 修改一组字符串的第j个字母使他们全部合法的转移方程是:
dp[i][state|set]=min{dp[i−1][state]+costset[j]}
我们只需要枚举每一个状态即可。
AC代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int dp[1<<21];
char s[26][26];
int c[26][26];
int v[26][26];
int state[26][26];
int n,m;
int main ( )
{
while ( ~scanf ( "%d%d" , &n , &m ) )
{
for ( int i = 0 ; i < n ; i++ )
scanf ( "%s" , s[i] );
for ( int i = 0 ; i < n ; i++ )
for ( int j = 0 ; j < m ; j++ )
scanf ( "%d" , &c[i][j] );
int total = (1<<n)-1;
for ( int i = 0 ; i < n ; i++ )
for ( int k = 0 ; k < m ; k++ )
{
int maxn = 0;
for ( int j = 0 ; j < n ; j++ )
{
if ( s[i][k] != s[j][k] ) continue;
v[i][k] += c[j][k];
maxn = max ( c[j][k] , maxn );
state[i][k] |= 1<<j;
}
v[i][k] -= maxn;
}
memset ( dp , 0x3f , sizeof ( dp ) );
dp[0] = 0;
for ( int i = 0 ; i < total ; i++ )
for ( int j = 0 ; ; j++ )
if ( !((i>>j)&1))
{
int a = 1<<j;
for ( int k = 0 ; k < m ; k++ )
{
int b = state[j][k];
dp[i|a] = min ( dp[i|a] , dp[i] + c[j][k] );
dp[i|b] = min ( dp[i|b] , dp[i] + v[j][k] );
}
break;
}
printf ( "%d\n" , dp[total] );
}
}