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;
}