题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4539
#include<bits/stdc++.h>
using namespace std;
#define debug puts("YES");
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define read(x,y) scanf("%d%d",&x,&y)
#define ll long long
#define lrt int l,int r,int rt
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define root l,r,rt
const int maxn =1e6+5;
const int mod=1e9+7;
const int ub=1e6;
ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
int n,m,x,row[110];///每一行的状态
int s[1<<11],cnt[1<<11];///合法状态和可以排布的士兵数量
int dp[110][220][220];
int getcnt(int x){int ret=0;while(x>0) x-=(x&(-x)),ret++;return ret;}
/*
中文题目。
状态压缩,
先找出状态转移方程,
dp[i][j][k]=max(dp[i-1][k][p]): 枚举p。
代表在第i行状态为j,第i-1行状态为k时的最大值。
因为我们可以看出当相距三行及以上时状态是独立的,
所以我们需要用DP暴力枚举当前状态的前两行。
那么下面就是状态压缩和位运算技巧了。
首先为了判定方便要把0压缩成1,因为我们答案要枚举的是
不含零的子集,所以把0压成1就可以用与运算直接判断是否符合要求。
我们对于每行先把符合要求的状态压出来,然后直接在符合要求的状态中进行枚举,
符合的条件是相邻间距不能有刚好为2的。
四层循环,后三层每一层都有条件,第二层是枚举当前row的1的子集即可,
第三层是i层与i-1层符合要求,就是不能有刚好错位的。
第四层是要求不能有和i层状态重复的,这很容易判得(不用麻烦的判定和i-1层的关系)
这也是状态转移的技巧吧。
*/
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=0;i<n;i++)
{
row[i]=0;
for(int j=0;j<m;j++)
{
scanf("%d",&x);
if(!x) row[i]=(row[i]<<1)|1;///把状态反过来这样方便点
else row[i]<<=1;
}
}
int idx=0,ans=0;
for(int i=0;i<(1<<m);i++)
{
if(i&(i<<2)) continue;
s[idx]=i,cnt[idx++]=getcnt(i);
}
memset(dp,0,sizeof(dp));///初始化为0还是必要的
for(int i=0;i<idx;i++)
{
if(s[i]&row[0]) continue;
dp[0][i][0]=cnt[i];
}///初始化DP状态数组
for(int i=1;i<n;i++)
{
for(int j=0;j<idx;j++)
{
if(s[j]&row[i]) continue;///必须是完全符合的才行
for(int k=0;k<idx;k++)
{
if( (s[j]&(s[k]>>1)) || (s[j]&(s[k]<<1)) ) continue;///
for(int p=0;p<idx;p++)
{
if( s[j]&s[p] ) continue;///有一个相同位都不行
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+cnt[j]);///状态转移方程
}
}
}
}
for(int i=0;i<idx;i++) for(int j=0;j<idx;j++) ans=max(ans,dp[n-1][i][j]);
printf("%d\n",ans);
}
return 0;
}