POJ3254

POJ3254Corn Fields

有一个n*m的矩阵,这个矩阵以01的形式给出,其中标为1的格子可以选择,标为0的格子不能被选。现在要你在可以选的所有格子中选择一种方案,使得被选的任意两个格子不邻接(即无公共边)。问你共有多少种选择方案?(被你选中的格子总数没有要求,一个格子都不选也可以作为一个方案)

输入:第一行为n*m(1<=n,m<=12),接下来n行,每行用0,1表示矩阵的一行。

输出:方案总数%10^8.

分析:

本题先假设以行为计算状态转移的基本单位,则假设处理第i行时,那么第i行能选哪几个格子只和第i-1行选格子的情况有关(当然也和第i行本身哪些格子能选有关)。

所以我们可以得出下面的状态转移方程:令d[i][s]表示第i行的选格情况为s时的选格子的总方案数。(以s的二进制形式表示选格子的情况,如s=0011时,选择第i行的第2个和第3个格子,从左到右,从0计数列号)

d[i+1][s1]=sum{d[i][s2]},其中n>i>=0,s1要与s2兼容(s1与s2兼容的意思是:s1与s2对应的二进制位上不会全都是1.比如1101与0001不兼容,第3位都为1),且还要求s1的二进制位合法(比如s1为1101,而矩阵给出的该行为0111,那么s1不行,因为矩阵该行第0位只能为0,不能被选,不能为1,且要求s1的二进制形式中没有连续的两个1)。

初值为:d[0][s1]=1,其中s1表示所有的第0行合法值。最终我们要求的结果是d[n-1][s2],s2表示第n-1行所有的合法值。不要忘了结果对MOD求余。

AC代码:32ms

#include<cstdio>
#include<cstring>
using namespace std;
const int MOD=100000000;
long long d[15][1<<13];//d[i][s]=3,表示第i行的选格情况为s(二进制)时的总方案数。
int a[1<<13];//用来保存初始给出的每行的01矩阵
bool fine(int S,int S1)//判断S与S1是否兼容,即对应的二进制形式中,第i位是否都是1,如果是返回false
{
    for(int i=0;i<12;i++)
        if(  ( S&(1<<i) ) && ( S1&(1<<i) ) )
            return false;
    return true;
}
bool legal(int S,int S1)//判断S与S1是否合法,S1有1的位S中才能是1
{
    for(int i=0;i<12;i++)
        if(  ( S&(1<<i) ) && !( S1&(1<<i) ) )
            return false;
    return true;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)==2&&n&&m)
    {
        for(int i=0; i<n; i++)
        {
            a[i]=0;
            for(int j=0; j<m; j++)
            {
                int x;
                scanf("%d",&x);
                a[i]<<=1;
                if(x)a[i]|=1;
            }
        }


        memset(d,0,sizeof(d));
        long long sum = 0;//记录最后总数
        for(int S=a[0]; S>=0; S=(S-1))if(legal(S,a[0])) //生成第0行所有的合法状态
        {
            bool ok = true;
            for(int j=0; j<m-1; j++) //对于该行不能有连续1相连
            {
                if(  ( S&(1<<j) ) && ( S&(1<<(j+1)) ) )
                {
                    ok = false;
                    break;
                }
            }
            if(ok)d[0][S]=1;
            if(n==1) sum = (sum+d[0][S])%MOD;//注意行数有可能为1,则只有第0行
        }
        for(int i=1; i<n; i++) //对于每一行
        {
            for(int S=a[i]; S>=0; S=(S-1))if(legal(S,a[i])) //生成该行所有合法状态
            {
                bool ok = true;//假设状态合法
                for(int j=0; j<m-1; j++) //对于该行不能有连续1相连
                {
                    if(  ( S&(1<<j) ) && ( S&(1<<(j+1)) ) )
                    {
                        ok = false;
                        break;
                    }
                }
                if(!ok)continue;
                for(int S1=a[i-1]; S1>=0; S1=(S1-1))if(legal(S1,a[i-1])) //生成上一行所有合法状态
                {
                    if(fine(S,S1))
                    {
                        d[i][S] += d[i-1][S1]%MOD;//如果兼容,则加上计数
                    }
                }
                if(i==n-1) sum = (sum +d[i][S])%MOD;
            }
        }
        printf("%I64d\n",sum);
    }
    return 0;
}


其实这道题目可以看出是有层次的DAG图,第0层图中假设有1101,0010,0101,0000是合法的,第1层中假设有0010,0000,0110合法等。然后0层的0000和1层的0000兼容,所以他们之间有一条有向边,我们要求的就是从第0层的合法节点到第n-1层的所有合法节点有多少条路。

   现在尝试用回溯法看下能不能解决。用a[15][15]来存下初始的01矩阵,然后在0的位置不能填数,在1的位置如果填数就变为2,如果不填就还是1.这要用dfs一个一个格子的构造,构造出整个矩阵,当构造完最后一个格子后计数变量++cnt。

构造回溯法的时候要注意:令a[r][c]=2时,这里a[r][c]的值改变了,如果不改回来,那么会对以后的dfs产生永久的影响,等于初始矩阵都变了,所以用回溯法时一定要注意,如果改了值一定要改回来。

实现代码:能算出正确结果,但运行时间太长,对于一个10*10的矩阵,不到100层递归,算了近半小时还算出来,指数级别的算法还是太长了。

#include<cstdio>
#include<cstring>
using namespace std;
int a[15][15];
int n,m,cnt;
const int MOD =100000000;
void dfs(int num)
{
    if(num==n*m)//构造完了所有格子
        cnt = (cnt+1)%MOD;
    else
    {
        dfs(num+1);
        int r = num/m;
        int c = num%m;
        if( a[r][c] && (c==0||a[r][c-1]!=2) && (r==0||a[r-1][c]!=2) )
        {
            a[r][c] = 2;//这里a[r][c]的值改变了,如果不改回来,那么会对以后的dfs产生永久的影响,等于初始矩阵都
                        //变了,所以用回溯法时一定要注意,如果改了值一定要改回来
            dfs(num+1);
            a[r][c]=1;
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n&&m)
    {
        cnt= 0;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
               scanf("%d",&a[i][j]);
            }
        }
        dfs(0);
        printf("%d\n",cnt%MOD);
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值