CF302E
题意
- 每一行为好记满足:在这一行至少有一个字母在这一列是只出现一次的。
- 修改每一个字母都要费用,求使每一行都好记的最小费用。
题解
- 使一行的字母好记,可以修改某一个字母,或者修改这一列的与这个字母相同的字母,并保留花费最大。
- 状态压缩枚举,dp[i]表示状态i的最小花费。i中1表示好记,0表示不好记。我们不用判断这一行是否已经是好记,直接暴力枚举。如果好记,根据上面的修改方案,花费就是0。
代码
#include <bits/stdc++.h>
using namespace std;
int const N = 20 + 2;
int const inf = 0x7f7f7f7f;
char s[N][N];
int n,m;
int a[N][N],sum[N][N],state[N][N];
int dp[1<<N];
int main(){
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",&a[i][j]);
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){ //找与s[i][j]同列的相同的字母的总花费
int mx = -1;
for(int k=0;k<n;k++){
if(s[i][j] == s[k][j]){
mx = max(mx,a[k][j]);
state[i][j] |= (1<<k);
sum[i][j] += a[k][j];
}
}
sum[i][j] -= mx; //如果没有相同的字母,那么sum[i][j]显然为0,修改不花费代价
}
}
memset(dp,inf,sizeof(dp));
dp[0] = 0; //初始化
for(int i=0;i<(1<<n);i++){ //枚举n行的状态,1表示好记,0表示不好记
for(int j=0;j<n;j++){ //遍历每一行
if((i&(1<<j)) == 0){ //假设第j行不好记
for(int k=0;k<m;k++){ //考虑第j行的每一字母
if(dp[i] == inf) continue;
dp[i|(1<<j)] = min(dp[i|(1<<j)],dp[i] + a[j][k]); //修改一行中的一个使其变为好记
dp[i|state[j][k]] = min(dp[i|state[j][k]],dp[i] + sum[j][k]); //修改和自己相同的,并保留最大的
}
}
}
}
printf("%d\n",dp[(1<<n)-1]);
return 0;
}