HDU-1565-方格取数(1)
http://acm.hdu.edu.cn/showproblem.php?pid=1565
我的第一个状态压缩DP
给你一个n*n的格子的棋盘,每个格子里面有一个非负数,从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大
3
75 15 21
75 15 28
34 70 5
188
对于每一个数字,或取或不取,记1为取该数,0为不取该数,对于每行的数来说,它的状态就可以用一个二进制的数来描述,对于第一行,若果我们取75,21,我们就可以用二进制的5来描述,即101,因为取的数所在的2个格子不能相邻,所以每一行的二进制数不能有相邻的1,再来看列,相邻的两行不能有相邻的,对于两个二进制,也就是两个数相与(&)为0,这样就可以得到当前的行和上一行的关系,dp[i][j]=dp[i-1][k]+sum(j)
dp[i][j]表示第i行在j状态,dp[i-1][k] 表示第i-1行在k状态,sum(j)表示第i行在状态k下所取数的和,当然k&j==0
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
int num[21];
int k,st[18800]; //共有k中状态
int dp[2][18800];
int map[25][25];
int n;
int max(int x,int y)
{
return x>y?x:y;
}
void init1() //初始化num,n位数2进制数最多有num[n]种
{
int i;
num[0]=1;
for(i=1;i<=20;i++)
num[i]=num[i-1]<<1;
}
int judge(int x)
{
int end=0;
while(x)
{
if(end==1&&x%2)
return 0;
end=x%2;
x/=2;
}
return 1;
}
void init2()
{
int i;
init1();
k=0;
for(i=0;i<=num[20];i++)
if(judge(i))
st[k++]=i;
//printf("%d\n",k); //k=17712,这个要事先求出才能知道数组该开多大
}
int main()
{
int i,j,h;
int Max,temp;
init2();
while(scanf("%d",&n)!=EOF)
{
if(n==0)
{
printf("0\n");
continue;
}
for(i=0;i<n;i++)
for(j=0;j<n;j++)
scanf("%d",&map[i][j]);
Max=-1;
memset(dp,0,sizeof(dp));
for(i=0;i<k;i++) //先算第一行
{
if(st[i]>=num[n]) //n位数2进制数最多有num[n]种
break;
for(j=0;j<n;j++) //把每种状态的数从右至左相加
if(st[i]&num[j])
dp[0][i]+=map[0][j];
if(dp[0][i]>Max)
Max=dp[0][i];
}
for(i=1;i<n;i++)
{
for(j=0;j<k;j++)
{
if(st[j]>=num[n])
break;
temp=0;
for(h=0;h<n;h++)
if(st[j]&num[h])
temp+=map[i][h];
for(h=0;h<k;h++)
{
if(st[h]>=num[n])
break;
if(!(st[h]&st[j]))
dp[1][j]=max(dp[1][j],dp[0][h]+temp);
Max=max(Max,dp[1][j]);
}
}
for(j=0;j<k;j++)
{
if(st[j]>num[n])
break;
dp[0][j]=dp[1][j];
dp[1][j]=0;
}
}
printf("%d\n",Max);
}
system("pause");
return 0;
}