状态压缩--方格取数
标签: ACM
题目:
给你一个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
解题思路:
- 找出每一行的状态的状态用二进制保存,0表示该位置不取,1表示取
- 将每一行所有状态的和保存到数组里面
- 从第二行开始找该行与上一行不冲突的所有状态,保存状态最大之和, 一直找到最后一行为止
- 遍历最后一行所有状态数值之和,最大值即为答案
需要用到异或,感觉在状态压缩里面特别重要
(1)按位与运算符(&)
按位与运算将两个运算分量的对应位按位遵照以下规则进行计算:
0 & 0 = 0, 0 & 1 = 0, 1 & 0 = 0, 1 & 1 = 1。
即同为 1 的位,结果为 1,否则结果为 0。
例如,设3的内部表示为011
5的内部表示为101
则3&5
的结果为001
(2)按位或运算符(|)
按位或运算将两个运算分量的对应位按位遵照以下规则进行计算:
0 | 0 = 0, 0 | 1 = 1, 1 | 0 = 1, 1 | 1 = 1
即只要有1个是1的位,结果为1,否则为0。
(3)按位异或运算符(^)
按位异或运算将两个运算分量的对应位按位遵照以下规则进行计算:
0 ^ 0 = 0, 0 ^ 1 = 1, 1 ^ 0 = 1, 1 ^ 1 = 0
即相应位的值相同的,结果为 0,不相同的结果为 1。
例如,013^035
结果为026
。
异或运算的意思是求两个运算分量相应位值是否相异,相异的为1,相同的为0。按位异或运算的典型用法是求一个位串信息的某几位信息的反。如欲求整型变量j的最右4位信息的反,用逻辑异或运算017^j,就能求得j最右4位的信息的反,即原来为1的位,结果是0,原来为0的位,结果是1。
AC代码
#include <iostream>
#include <string.h>
#define M 17720 //每一行有最大17711种状态
#define N 21
using namespace std;
int map[N][N]; //储存输入的数字
int sum[N][M]; //第i行第j种状态的总和
int state[M];
int dp[N][M];
int n,p;
int max(int a,int b) // 求最大值
{
return (a>b?a:b);
}
bool checkLine(int i) //检查该状态是否符合题意
{
return !(i&(i>>1));
}
bool checkTwoLine(int i,int j) //检查该行与上一行是否符合题意
{
return !(i&j);
}
void init()
{
int i,j,k;
p=0;
memset(sum,0,sizeof(sum));
for(i=0;i<(1<<n);i++) //将n改为20测试获得每一行有17711种状态
if(checkLine(i))
state[p++]=i;
for(i=0;i<n;i++)
for(j=0;j<p;j++)
for(k=0;k<n;k++)
if((state[j]>>k)&1)
sum[i][j]+=map[i][n-k-1];
}
void solve()
{
int i,j,k;
memset(dp,0,sizeof(dp));
for(i=0;i<p;i++)
dp[0][i]=sum[0][i];
for(i=1;i<n;i++) //遍历每一行
for(j=0;j<p;j++) //遍历该行的每一种状态
for(k=0;k<p;k++) //遍历上一行的每一种状态
{
if(checkTwoLine(state[j],state[k]))
dp[i][j]=max(dp[i][j],sum[i][j]+dp[i-1][k]);
}
int ans=dp[n-1][0];
for(i=1;i<p;i++)
{
if(ans<dp[n-1][i])
ans=dp[n-1][i];
}
cout<<ans<<endl;
}
int main()
{
while (cin>>n)
{
int i,j;
memset(map,0,sizeof(map));
for(i=0;i<n;i++)
for(j=0;j<n;j++)
cin>>map[i][j];
init();
solve();
}
return 0;
}