题目
题目描述 Problem Description
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
输入 Input
包括多个测试实例,每个测试实例包括一个整数 和 个非负数( )
输出 Output
对于每个测试实例,输出可能取得的最大的和
样例输入 Sample Input
3
75 15 21
75 15 28
34 70 5
样例输出 Sample Output
188
题解
思路
我们可以自上而下、一行行地选择格子。在一行内选择格子的时候,只和上一行的选择方案有关,我们就可以将” 当前放到第几行、当前行的选择方案”作为状态进行动态规划。
这里,我们就要用到状态压缩: 一行里被选择的格子实际上是一个集合, 我们要将这个集合压缩为一个整数。比如,对于一个 列的矩阵,如果当前行的状态是 ,那么就意味着当前行选择了第一 个和第三个格子;类似地,如果当前行的状态是 ,那么就意味着当前行选择了第-个和第二个格子 (当然由于计算顺序的缘故,我们通常会把 当做选择倒数第一个和倒数第二 个格子, 这并不影响我们理解这道题的解法)。
实现
如果上一行的状态是now
, 下一行的状态是prev
, 那么我们只需要确保上下两行的选择方案里没有重复的元素,也就是(now & prev)==0
就可以了。
此外,我们还需要判断当前行的状态是否合法,因为读入的矩阵中并不是每个格子都可以选择的,如果我们将矩阵中每行的值也用状态压缩来存储,不妨记为flag,那么当前行选择的格子集合一定包含于当前行合法格子的集合,也就是说,(now | flag) == flag
必须成立;同时行内也不能选择相邻的,也就是now & (now >> 1) ==0
必须成立。
这样,我们就可以通过枚举上一 行的所有状态,来更新当前行、当前状态的最优解了。直到算完最后一行,统计一下所有状态的最大值即可。
代码:
#include<iostream>
#include<cstdio>
using namespace std;
int a[21][20];
int state[21];//初始每行的状态
int dp[21][1<<20];
bool ok(int now){//判断行内是否相交
return(now&(now>>1))==0;
}
bool fit(int now,int i){//用来判断now这个选取状态是否和符合第i行的输入
return(now | state[i])==state[i];
}
bool not_intersect(int now, int prev){//判断状态now和状态prev是否能放在相邻的行
return(now&prev)==0;
}
int count(int now){//统计now状态选了多少个元素
int s=0;
while(now){
s+=(now & 1);
now>>=1;
}
return s;
}
int main(){
int n, m;
scanf("%d %d",&n,&m);
//行从1开始,列从0开始,这样后面处理起来方便
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++){
cin >> a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++){
if(a[i][j]){
state[i]+=(1 << j);
}
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<(1<<m);j++){
if(ok(j) && fit(j,i)){
for(int k=0;k<(1<<m);k++){
if(ok(k)&&fit(k,i-1) && not_intersect(j,k)){
dp[i][j]=max(dp[i][j],dp[i-1][k]+count(j));
}
}
}
}
}
int ans=0;
for(int i=0;i<(1<<m);i++){
ans=max(ans,dp[n][i]);
}
printf("%d",ans);
return 0;
}