给你一个N*N(3<=N<=15)矩阵,矩阵中每个格子都是一个两位正整数,现在要你取数,使得所取的所有数的总和最大.规则:不能取任意两个邻接的数(8个方向邻接).
分析:第i行怎么取只和第i-1行的取法有关,令d[i][S]表示第i行的取法是S时能取得最大数之和.
d[i][S]=max{d[i-1][S1] + get_v(i,S)} 其中S1和S是兼容的,get_v(i,S)得到的是第i行为状态S时从第i行能获得的数总和.
初值:d[0][S]=x,S表示第0行的所有合法状态.其他d初始化为-1,行号从0到N-1,列号也从0到N-1.
AC代码:枚举有效状态,843ms
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int d[16][1<<15];
int n;
int g[16][16];
int cnt;
int state[2000];//1<<15中共1597个合法状态
bool legal(int S)//S中没有连续的1
{
for(int i=0;i<n-1;i++)
if( S&(1<<i) &&S&(1<<(i+1)) )
return false;
return true;
}
int get_v(int i,int S)//得到第i行状态为S时取得数和
{
int sum=0;
for(intj=0;j<n;j++)if(S&(1<<j))
sum += g[i][j];
return sum;
}
bool campet(int S,int S1)//判断S和S1是否兼容
{
if(S&S1)//对应列有1
return false;
else if( S&(S1<<1) )//S中列与S1中的右上角列有1
return false;
else if( (S<<1)&S1 )//S中列与S1中的左上角列有1
return false;
return true;
}
int main()
{
char a;
n=0;
while(scanf("%d%c",&g[0][n++],&a)==2)//读取第一行的第一个数和它后面的一个字符
{
while(scanf("%d%c",&g[0][n++],&a)==2)//读取第一行的第2到n个数
{
if(a=='\n')
{
break;
}
}
cnt= 0;//计数有效状态
for(int i=1;i<n;i++)//读取第2到n行
for(int j=0;j<n;j++)
scanf("%d",&g[i][j]);
for(int S=0;S<(1<<n);S++)if(legal(S))//预先求出所有有效状态
state[cnt++]=S;
memset(d,-1,sizeof(d));
for(int i=0;i<cnt;i++)//求出d的第0行初值,行列号从0到n-1
{
int S=state[i];
d[0][S] = 0;
for(int j=0;j<n;j++)
{
if(S&(1<<j))
d[0][S]+=g[0][j];
}
}
int sum=0;
for(int i=1;i<n;i++)//第i行
{
for(int j=0;j<cnt;j++)//第i行选第j个合法状态
{
int S = state[j];
for(int k=0;k<cnt;k++)//第i-1行选第k个合法状态
{
int S1 = state[k];
if(!campet(S,S1) ||d[i-1][S1]==-1 )//不兼容或者状态S1不可到达
continue;
d[i][S] = max(d[i][S] ,d[i-1][S1]+get_v(i,S));
if(i==n-1) sum = max(sum ,d[i][S]);
}
}
}
printf("%d\n",sum);
n=0;
}
return 0;
}
速度有点慢,现在用单层dfs回溯构造法来做而不是用枚举所有有效状态来做.
AC代码:593ms
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int d[16][1<<15];
int n,sum;
int g[16][16];
int cnt;
int state[2000];//1<<15中共1597个合法状态
bool legal(int S)//S中没有连续的1
{
for(int i=0;i<n-1;i++)
if( S&(1<<i) &&S&(1<<(i+1)) )
return false;
return true;
}
int get_v(int i,int S)//得到第i行状态为S时取得数和
{
int sum=0;
for(intj=0;j<n;j++)if(S&(1<<j))
sum += g[i][j];
return sum;
}
bool campet(int S,int S1)//判断S和S1是否兼容
{
if(S&S1)//对应列有1
return false;
else if( S&(S1<<1) )//S中列与S1中的右上角列有1
return false;
else if( (S<<1)&S1 )//S中列与S1中的左上角列有1
return false;
return true;
}
void dfs(int r,int c,int S,int S1)//要在第i行选,当前第i行的状态是S,第i-1行的状态是S1
{
if(c==n)return ;
d[r][S] = max(d[r][S],d[r-1][S1]+get_v(r,S));//第c列不选
if(r==n-1) sum = max(sum,d[r][S]);
if( (S1&(1<<c))==0 && (c==0 || ( (S&(1<<(c-1)))==0 && (S1&(1<<(c-1)))==0 )) && ( c==n-1 || ( (S1&(1<<(c+1)))==0 ) ) )
//第c列选,需要符合的条件:S1第c列没数且S和S1的c-1列和c+1列都没数
{
S+=(1<<c);
d[r][S] =max(d[r][S],d[r-1][S1]+get_v(r,S) );//第c列选
if(r==n-1) sum = max(sum,d[r][S]);
dfs(r,c+1,S,S1);
S-=(1<<c);//还原状态
}
dfs(r,c+1,S,S1);
}
int main()
{
char a;
n=0;
while(scanf("%d%c",&g[0][n++],&a)==2)//读取第一行的第一个数和它后面的一个字符
{
while(scanf("%d%c",&g[0][n++],&a)==2)//读取第一行的第2到n个数
{
if(a=='\n')
{
break;
}
}
cnt= 0;//计数有效状态
for(int i=1;i<n;i++)//读取第2到n行
for(int j=0;j<n;j++)
scanf("%d",&g[i][j]);
for(int S=0;S<(1<<n);S++)if(legal(S))//预先求出所有有效状态
state[cnt++]=S;
memset(d,-1,sizeof(d));
for(int i=0;i<cnt;i++)//求出d的第0行初值,行列号从0到n-1
{
int S=state[i];
d[0][S] = 0;
for(int j=0;j<n;j++)
{
if(S&(1<<j))
d[0][S]+=g[0][j];
}
}
sum=0;
for(int i=1;i<n;i++)//第i行
{
for(int j=0;j<cnt;j++)//第i-1行选第j个合法状态
{
int S1 = state[j];
if(d[i-1][S1]==-1)
continue;
dfs(i,0,0,S1);
}
}
printf("%d\n",sum);
n=0;
}
return 0;
}
AC代码:31ms 双层回溯滚动数组+状态压缩DP
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int d[2][1<<15];
int n,sum;
int g[16][16];
int cnt;//有效状态计数
int cur;//滚动数组指针
int state[2000];//1<<15中共1597个合法状态
bool legal(int S)//S中没有连续的1
{
for(int i=0;i<n-1;i++)
if( S&(1<<i) && S&(1<<(i+1)) )
return false;
return true;
}
void dfs(int r,int c,int S,int S1,int num)//要在第r行c列,r行为S,r-1行为S1,当前r行S选的数和位num
{
if(c==n)
{
d[cur][S] = max(d[cur][S],d[1-cur][S1]+num);//第c列不选
if(r==n-1) sum = max(sum , d[cur][S]);
return ;
}
if(c<=n-2)//上或下行放一个1
{
dfs(r,c+2,S+(1<<c),S1,num+g[r][c]);//r行放
dfs(r,c+2,S,S1+(1<<c),num);//r-1行放
}
else//c列已经是最后一列了
{
dfs(r,c+1,S+(1<<c),S1,num+g[r][c]);//r行放
dfs(r,c+1,S,S1+(1<<c),num);//r-1行放
}
dfs(r,c+1,S,S1,num);//不放
}
int main()
{
char a;
n=0;
while(scanf("%d%c",&g[0][n++],&a)==2)//读取第一行的第一个数和它后面的一个字符
{
while(scanf("%d%c",&g[0][n++],&a)==2)//读取第一行的第2到n个数
{
if(a=='\n')
{
break;
}
}
cnt= 0;//计数有效状态
for(int i=1;i<n;i++)//读取第2到n行
for(int j=0;j<n;j++)
scanf("%d",&g[i][j]);
for(int S=0;S<(1<<n);S++)if(legal(S))//预先求出所有有效状态
state[cnt++]=S;
memset(d,-1,sizeof(d));
for(int i=0;i<cnt;i++)//求出d的第0行初值,行列号从0到n-1
{
int S=state[i];
d[0][S] = 0;
for(int j=0;j<n;j++)
{
if(S&(1<<j))
d[0][S]+=g[0][j];
}
}
sum=0;
cur=0;
for(int i=1;i<n;i++)//第i行
{
cur=1-cur;
dfs(i,0,0,0,0);
}
printf("%d\n",sum);
n=0;
}
return 0;
}