• P1985 [USACO07OPEN]翻转棋 题解


    CSDN同步

    原题链接

    简要题意:

    给定一个 (01) 棋盘,每次可以翻转一个“十”字形(即一个格子连同它四方向的相邻格子,出界则不翻)。求在哪些格子上翻转(十字形的中心)可以使得 翻转后全 (0) 且 方案字典序最小

    首先 (n,m leq 15),本着面向数据范围做题的原理,分析算法。

    算法一

    枚举翻转哪些格子进行验证。

    时间复杂度:(O(2^{n imes m} imes n imes m)),难以接受这样的爆炸性复杂度。

    算法二

    需要我们分析一下题目。

    我们肯定无法枚举 (n) 行所有的翻转情况,但我们可以枚举 (1) 行。

    • 什么?枚举 (1) 行?

    • 没错,我们只枚举第 (1) 行是否翻转。

    • 那么,其它行的怎么办呢?

    • 其实很简单。你想:在 按照行的顺序枚举 的情况下,如果 (a_{i-1,j} = 0),那么 (a_{i,j}) 肯定不翻;因为,其它能够改变 (a_{i-1,j}) 的格子已经全部确定,再翻成 (1) 就翻不回去了。 那同理,如果 (a_{i-1,j}=1),那么 (a_{i,j}) 肯定翻,因为 其它能够改变 (a_{i-1,j}) 的格子已经全部确定,不翻成 (0) 也就翻不回去了。

    思路基本成型:枚举第一行的翻转状态,以此递推出每一个格子的翻转状态,模拟翻转验证,记录字典序最小即可。

    那么,无解是什么情况呢?

    你会发现,如果最后无解的,那就肯定所有的 (1) 都在最后一行。

    因为,前面的都已经被下面的格子重新翻回去了,而最后一行每人会翻它们。

    这样子枚举即可。

    时间复杂度:(O(2^m imes n imes m)),大概 (7.3 imes 10^6),可以通过。

    实际得分:(100pts).

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=20;
    
    inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
    	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
    
    int h[N],a[N][N];
    int n,m,b[N][N];
    int turn[N][N],p[N][N];
    int rev[N],ans=INT_MAX;
    
    const int dx[5]={0,0,0,1,-1};
    const int dy[5]={0,1,-1,0,0};
    
    inline void _rev_(int x,int y) {
    	for(int i=0;i<5;i++) {
    		int nx=x+dx[i],ny=y+dy[i];
    		if(nx<1 || ny<1 || nx>n || ny>m) continue;
    		b[nx][ny]^=1;
    	} //模拟翻转过程
    }
    
    inline void check() {
    	memset(turn,0,sizeof(turn));
    	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) b[i][j]=a[i][j]; //备份,准备翻转
    	for(int i=1;i<=m;i++)
    		if(rev[i]) {
    			_rev_(1,i); turn[1][i]=1;
    		} //先翻第一行
    	for(int i=2;i<=n;i++) for(int j=1;j<=m;j++)
    		if(b[i-1][j]) turn[i][j]=1,_rev_(i,j); //依次确定后面的行
    	for(int i=1;i<=m;i++) if(b[n][i]) return; //最后一行判断
    	int s=0;
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++) if(turn[i][j]) s++; //记录 1 的个数,便于字典序排序
    	if(s<ans) { //较小
    		for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++) p[i][j]=turn[i][j];
    		ans=s; //更新答案
    	}
    }
    
    inline void dfs(int x) { //x 表示当前枚举的是 1 行 x 列
    	if(x>m) { //验证
    		check(); return;
    	} for(int i=0;i<=1;i++)
    		rev[x]=i,dfs(x+1); //是否翻转当前格
    }
    
    int main(){
    	n=read(),m=read();
    	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read();
    	dfs(1); //枚举第一行状态
    	if(ans==INT_MAX) puts("IMPOSSIBLE"); //无解
    	else 
    		for(int i=1;i<=n;i++) {
    			for(int j=1;j<=m;j++) printf("%d ",p[i][j]);
    			puts("");
    		} //输出答案
    	return 0;
    }
    
    
  • 相关阅读:
    图像旋转与图像缩放及Matlab代码实现
    主成分分析 matlab手把手教操作、SPSS、python实例分析
    直方图均衡化与Matlab代码实现
    kNN算法基本原理与Python代码实践
    MATLAB 均方根误差MSE、两图像的信噪比SNR、峰值信噪比PSNR、结构相似性SSIM
    vue实战——对没见过的东西的科普以及第一次踩坑
    记录关于js模块的浅薄探索(一)——从别人博客中的理解
    js中的同步异步
    webpack踩坑记录(一)
    vue入门(二) ——监听属性,样式绑定
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12636687.html
Copyright © 2020-2023  润新知