HDU2167

HDU2167Pebbles

给你一个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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值