在一个棋盘上,每个格子上都是0或者1,现在有两种翻法,一个是翻当前点和上下左右的点,另一个是只翻上下左右的点,不翻当前点。当前点必须是棋盘里边的点。问最少翻多少次可以将棋盘中的点都翻成0。
因为这两种翻法仅有当前点不同,所以如果进行了一次操作,那么我们无须关心这次操作是哪次操作,因为两种操作对周围格子的影响是相同的,也无须关心当前点是什么颜色了,因为总有一种方法是让当前点变成0的。如果进行了两次操作,我们可以认为他没有进行操作,然后当前点的颜色反转了一下。
所以我们每个格子仅需要记录3种状态中的一种即可,进行了一次操作,或者没有进行操作(包含进行偶数次操作),颜色为0,没有进行操作,颜色为1。
然后插头DP转移即可。题解说叫轮廓线DP..好像是一个东西..
复杂度n*m*3^m*3。
#include <cstdio>
int dp[2][59049];
int pow3[11];
int n,m;
char a[10][10];
inline int get(int o,int i) {
if (i<0) return 0;
return o/pow3[i]%3;
}
inline int set(int o,int i,int x) {
return o%pow3[i]+o/pow3[i+1]*pow3[i+1]+x*pow3[i];
}
inline void update(int &a,int b) {
if (a==-1||a>b) a=b;
}
bool right(int o) {
for (int j=0;j<m;j++)
if (get(o,j)==2) return false;
return true;
}
void calDP(int i,int j,int fromdp[],int todp[]) {
int tmpk,tmpo;
for (int k=0;k<pow3[m];k++) {
//printf("%d %d %d%d%d %d\n",i,j,k%3,k/3%3,k/9,fromdp[k]);
if (fromdp[k]==-1) continue;
int kj=get(k,j),kj1=get(k,j-1);
//0
tmpk=set(k,j,0);
if (kj1==1) tmpk=set(tmpk,j-1,2);
else if (kj1==2) tmpk=set(tmpk,j-1,1);
tmpo=1;
if (kj==1) tmpo+=2;
update(todp[tmpk],fromdp[k]+tmpo);
//1
tmpk=set(k,j,1);
tmpo=0;
if (kj==2) tmpo+=2;
if ((a[i][j]=='1')^(kj==0)^(kj1==0)) tmpo+=2;
update(todp[tmpk],fromdp[k]+tmpo);
//2
tmpk=set(k,j,2);
tmpo=0;
if (kj==2) tmpo+=2;
if ((a[i][j]=='0')^(kj==0)^(kj1==0)) tmpo+=2;
update(todp[tmpk],fromdp[k]+tmpo);
}
}
int main() {
int i,j,k,cas=1;
pow3[0]=1;
for (i=1;i<11;i++) pow3[i]=pow3[i-1]*3;
while (scanf("%d%d",&n,&m),n) {
for (i=0;i<n;i++)
for (j=0;j<m;j++) scanf(" %c",&a[i][j]);
for (i=0;i<n;i++) a[i][0]^=1;
for (j=0;j<m;j++) a[0][j]^=1;
for (k=0;k<pow3[m];k++) dp[0][k]=dp[1][k]=-1;
dp[0][0]=0;
for (i=0;i<n;i++)
for (j=0;j<m;j++) {
int tmp=i*m+j&1;
calDP(i,j,dp[tmp],dp[tmp^1]);
for (k=0;k<pow3[m];k++) dp[tmp][k]=-1;
}
int ans=-1;
i=n*m&1;
for (k=0;k<pow3[m];k++) {
if (right(k)) update(ans,dp[i][k]);
}
printf("Case #%d: %d\n",cas++,ans);
}
return 0;
}