前两天打了一场比赛,状压不会,再次发现知识短板,哭...
第一次学的时候是大一学的,但是失败了,没学会,后来心理阴影总感觉很难,就没再学过。
这两天又重新学了一下。
谈一谈入门题~poj3254
题意:有一个n*m的0-1矩阵草地,1代表在这里可以放牛,0代表不能放牛。每两头牛不能相邻(左右相邻或者上下相邻),问有多少种放牛的方法。
直接进入正题:
(以下皆为学习完状态压缩后的personal comprehension,如若不对,敬请大佬指出~)
简单的说,状态压缩就是把问题的状态用一个十进制表示。一般的,问题的状态只有两种,走或者不走,选或者不选,放或者不放,我们都可以用0和1来表示这两种状态,但是如果单单用0和1来表示问题所有的状态,那么所涉及到的情况会有很多,所以我们把它转化为十进制,并且用 按位与,按位或,异或,左移右移等这些操作,可以方便且快速的得到问题的状态和结果。
回到问题,对于样例:
1 1 1
0 1 0
我们发现,总共有三列,也就是说,对于每一行,假设这一行所有的草地都能放牛,即输入为1 1 1的时候(样例的第一行),
我们可以得到下面
0 0 0; 0 0 1;0 1 0;1 0 0;1 0 1 五种情况,它分别对应十进制
0 1 2 4 5
也就是说,对于m=3的时候,每行放牛的情况最多就只有 这五种,我们先不要去想整个矩阵,只需要单独考虑一行,因为我们可以在后续的操作中通过和上一行的状态比较,来删除掉一些不符合情况的状态。
即:对于输入的m最多有(2^m)- 1 种状态,这些状态是可以预处理出来的,只需要让两个1不相邻即可,如下:
void init()
{
len=0;
for(int i=0; i<(1<
我们已经分析完了第一行的状态,一共有五种。
那么我们看第二行的输入为 0 1 0,值为0的草地不能放牛,所以这一行只有两种状态,即:
0 0 0 ; 0 1 0,分别代表十进制
0 2
这样,我们就可以拿这一行的每个状态和上一行的每个状态比较,去除掉上下相邻的情况,得到最终的答案。
设dp[ i ] [ state[j] ] 为对于第 i 行,第state[j]中状态时,放牛的方案总数、
那么对于每一行的状态state[j],我们都可以从上一行所有的状态里得到。
即动态转移方程为:
dp[j][state[j]]=dp[i-1][state[1]]+dp[i-1][state[2]]+...+dp[state[len],其中len为所有状态总数。
当然,其中还有不满足条件的,我们还需要加判断(详见代码)
到这里,状态压缩的过程基本就算完了。
综上所述,对于这道题,解决问题有如下三步:
第一步:预处理所有可能的状态:
void init()
{
len=0;
for(int i=0; i<(1<
第二步:统计出每行所有的状态。
其实这道题,把1设为可放牛,0设为不可放牛会更好理解些,不过问题不大,只是状态的一个逆。
怎么得到这一行所有的状态呢?举个栗子:
要得到第二行的状态,即 0和2,现在我们知道所有的状态有0 1 2 4 5.
当我们输入到第二行的时候,如我们输入的是 0 1 0
我们可以看成是1 0 1,然后把这个数转为十进制->5
用5(1 0 1)&按位与(所有的状态),如果得到的结果为不为0,那么肯定不符合条件。
比如上面按位与之后
0 0 0 0 0 1 0 1 0 1 0 0 1 0 1
& 1 0 1 1 0 1 1 0 1 1 0 1 1 0 1
= 0 0 0 0 0 1 0 0 0 1 0 0 1 0 1
我们只取结果为0的状态,即 0 0 0 和 0 1 0,就是上面我们所提到的符合条件的状态。
//输入处理
for(int i=1; i<=n; i++)
{
row[i]=0;
for(int j=1; j<=m; j++)
{
int x;
scanf("%d",&x);
if(!x)
row[i]+=1<<(m-j);
}
}
第三步:预处理第一行的方案数
这个就不多说了
for(int i=0; i
第四步:去除掉不满足条件的状态,统计对于当前状态所有的方案数。
鉴于题目中要求上下也不能相邻,所以我们还需要加一个判断条件来去掉上下相邻的情况,这个很简单,只需要按位与上两个状态,结果为0就符合条件,否则不符合。
if(state[k]&state[j])
continue;//不符合条件
所以总的代码如下:
#include
#include
#include
#include
#include
#define max(a,b) (a>b?a:b)
#define min(a,b) (a
写了好久,洗澡睡觉~