HDU-1565-方格取数(1)
Problem Description
给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
Input
包括多个测试实例,每个测试实例包括一个整数n 和n*n个非负数(n<=20)
Output
对于每个测试实例,输出可能取得的最大的和
Sample Input
3
75 15 21
75 15 28
34 70 5
Sample Output
188
题目简述
利用状态压缩,把每一行的一种取数方法压缩到一个二进制数,然后对于每行的cnt种取数方法,从第1行到第n行进行dp求解
每一行的dp最大值都和上一行的各种取数情况下的dp值有关
状态压缩:每一行有n个格子,将其对应于个数,转换成二进制后每一位上,1表示取该数,0表示不取该数。
位运算:a&b,如果某一位上都为1,则结果中该位也为1,否则为0
states[M]:每一种状态对应的取数方法(对于确定的i,states也是确定的)
andans[N][M]:第i行第j种情况下,第i行的总值
dp[N][M]:第i行第j种情况下,第1行到第i行的总值
状态转移方程:dp[i][j]=max(dp[i][j],dp[i-1][k]+andans[i][j])
重要雷区:运算优先级!!! (a&b)==0和a&b==0不一样!!!
输入输出
输入:无难点
输出:无难点
使用模板(无)
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 22
#define M 30000
using namespace std;
int states[M];
int andans[N][M];
int dp[N][M];
int mp[N][N];
int cnts;
void find_states(int n){ //寻找每个n对应的情况总数,并把相应可选的情况记入数组
int sum=1<<n; //相当于2^n
cnts=1;
for(int i=0;i<sum;i++){
if((i&(i<<1))==0){ //如果情况i中没有两个相邻数位都是1的情况,就记入数组
states[cnts++]=i;
}
}
}
void read(int n){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&mp[i][j]);
}
}
}
void init(){
memset(states,0,sizeof(states));
memset(dp,0,sizeof(dp));
memset(andans,0,sizeof(andans));
}
void sum_row_states(int n,int cnt){
for(int i=1;i<=n;i++){
for(int j=1;j<=cnt;j++){ //对每种情况遍历
int sum=n;
for(int k=1;k<(1<<n);k=(k<<1)){
if((k&states[j])!=0){ //如果第sum位上是1,则把值加入andans[i][j]中
andans[i][j]+=mp[i][sum];
}
sum--;
}
}
}
}
int dp_f(int n,int cnt){ //主算法
for(int i=1;i<=cnt;i++)dp[1][i]=andans[1][i];
for(int i=2;i<=n;i++){
for(int j=1;j<=cnt;j++){
for(int k=1;k<=cnt;k++){
if((states[j]&states[k])==0){ //第i行的j情况和i-1行的k情况符合条件
dp[i][j]=max(dp[i][j],dp[i-1][k]+andans[i][j]);
}
}
}
}
int sum=0;
for(int i=1;i<=cnt;i++){
sum=max(sum,dp[n][i]);
}
return sum;
}
int main(){
int n;
while(scanf("%d",&n)!=EOF){
//预处理
init();
find_states(n);
read(n);
sum_row_states(n,cnts);
//计算
int ans=dp_f(n,cnts);
printf("%d\n",ans);
}
return 0;
}