[HDOJ 4949] Light [插头DP]

本文介绍了一种使用轮廓线DP方法解决棋盘翻转问题的算法,详细解释了DP转移过程和复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在一个棋盘上,每个格子上都是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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值