第一章例题7偶数矩阵UVa 11464(枚举)

11464 - Even Parity

Time limit: 3.000 seconds

Even  Parity

Input: Standard Input

Output: Standard Output

We have a grid of size Nx N. Each cell of the grid initially contains a zero(0) or a one(1).
The parity of a cell is the number of 1s surrounding that cell. A cell is surrounded by at most 4 cells (top, bottom, left, right).

Suppose we have a grid of size 4x 4:

 

1

0

1

0

The parity of each cell would be

1

3

1

2

1

1

1

1

2

3

3

1

0

1

0

0

2

1

2

1

0

0

0

0

0

1

0

0

For this problem, you have to change some of the 0s to 1s so that the parity of every cell becomes even. We are interested in the minimum number of transformations of 0 to 1 that is needed to achieve the desired requirement.

Input

The first line of input is an integer T (T<30) that indicates the number of test cases. Each case starts with a positive integer N(1≤N≤15). Each of the next N lines contain N integers (0/1) each. The integers are separated by a single space character.

Output

For each case, output the case number followed by the minimum number of transformations required. If it's impossible to achieve the desired result, then output -1 instead.

Sample Input                             Output for Sample Input

 

3
3
0 0 0
0 0 0
0 0 0
3
0 0 0
1 0 0
0 0 0
3
1 1 1
1 1 1
0 0 0

Case 1: 0
Case 2: 3
Case 3: -1


Problem Setter: Sohel Hafiz,

Special Thanks: Derek Kisman, Md. Arifuzzaman Arif

 

 

/*此题完全借鉴了刘汝佳大神的思路,但是书上没有太多的解释,我只是多罗嗦了几句*/

/*这道题最暴力的方法就是对于n×n的方阵,对每个元素的“改”与“不改”进行枚举一遍,
看看每种状况之下的矩阵是否是偶数矩阵。但是由于01方阵的最大边长为15,
最差的情况下要枚举的次数为2^255=5*10^67,显然要超时,这样做是不行的。
但是由于n最大为15,我们可以暴力枚举第一行,只有不超过2^15=32768种可能。
接下来我们可以由第一行推出第二行,由第二行推出第三行,依次类推,
最后的时间复杂度为O(2^n*n^2),在n最大为15,t最大为30的情况下还是可以忍受的
*/

/*最后的难点为如何枚举第一列,如何由前一列递推出下一列*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=20;
const int INF=1000000;//无穷大
int n,a[maxn][maxn],b[maxn][maxn];/*a是原矩阵,b是被修改之后的矩阵,用来和a对照*/
int main()
{
 int check(int s);
 int t,kase=0,i,j;
 cin>>t;
 while(t--)
 {
  kase++;
  cin>>n;
  for(i=0;i<n;i++)
   for(j=0;j<n;j++)
   {scanf("%d",&a[i][j]);}
  int ans=INF;
  for(i=0;i<(1<<n);i++)
  {ans=(ans>check(i))?check(i):ans;}//ans取最小值
  if(ans==INF){ans=-1;}/*如果均不可以修改,则输出结果要更新为-1*/
  printf("Case %d: %d\n",kase,ans);
 }
 return 0;
}

int check(int s)//s是我们要枚举的第一行中的某一种情况,即s的范围是0~2^n-1
{
 int i,j;
 memset(b,0,sizeof(b));
 /*首先我们对第一行进行处理,如果我们所要枚举的某一个s在那一位是等于1的,则没有必要进行过多的判断,只要把
 b数组的相应位置赋值为1即可,留下来与a数组进行对照。如果s的某一位是0,则我们就要看看a的那一位到底是不是0了,
 如果是1的话就要返回了,因为a中的1是不可以改为0的。这样,第一行的可行性就搞定了*/
 for(i=0;i<n;i++)
 {
  if(s&(1<<i)){b[0][i]=1;}//如果假设的s的那一位是1则只要给b的相应位置赋值为1即可
  else if(a[0][i]==1){return INF;}//1不能被修改成0
 }
 /*下面我们就要在虚拟数组b中通过上一行推出下一行,推出一个全新的数组b,但是在推出的过程中和第一行时的可行性一样,
 只要中途发现推出来的b的某一个元素是0,我们就要回到a中去看相应位置的元素,如果是1则退出,因为1不能改为0;
 如果推出来b数组的相应位置为1,则不用管a中相应位置是否为1,因为如果a中相应位置为1则修改次数不会增加,如果为0,,
 则修改次数要加1,是吧,哈哈哈,下面让我们看看是怎么推的啊*/
 for(i=1;i<n;i++)
  for(j=0;j<n;j++)
  {
   int sum=0;//元素b[i-1][j]的上、左、右3个元素之和
 /*为什么要算b[i-1][j]的上、左、右3个元素之和呢,因为b[i-1][j]在b[i][j]的正上方位置,
  只要算出了sum,也就是4个邻接位置中的3个位置的和算出来了,则剩下的就应该是b[i][j]了。
  那么怎么算b[i][j]呢?如果sum是奇数,则要保证为偶数矩阵则b[i][j]应该为1;如果sum是偶数,
  则b[i][j]就应该是0,即b[i][j]=sum%2;*/
   if(i>1)sum+=b[i-2][j];/*这是算b[i-1][j]上面的那一块,只有i>1的时候才存在上面的那一块*/
   if(j>0)sum+=b[i-1][j-1];/*这是算b[i-1][j]左边的那一块,只有j>1的时候才存在左边的那一块*/
   if(j<n-1)sum+=b[i-1][j+1];/*这是算b[i-1][j]右边的那一块,只有j<n-1的时候才存在这一块*/
   b[i][j]=sum%2;/*算出b[i][j]*/
   if(b[i][j]==0&&a[i][j]==1){return INF;}//1不能被修改
  }
  /*在所有的b[i][j]均满足条件的情况下,我们下面将b数组和a数组的每一个元素进行比对,如果不同则证明要修改,
  相应的修改次数加1*/
  int ans=0;
  for(i=0;i<n;i++)
   for(j=0;j<n;j++)
   {
    if(b[i][j]!=a[i][j])
    {ans++;}
   }
  return ans;
}
/*对于check函数,我还有一点话要说,
为什么在不满足修改条件下我们输出的是INF,
而不是像题目中要求的那样返回-1呢?
由于我们要对s在0~2^n-1范围内进行枚举,如果有成功的显然我们
要成功修改里面的次数的最小值,这个最小值显然是个整数,但是
如果我们的返回值是-1就导致收不到那个满足条件的最小的非负整数值了。
因此我们在不满足条件时输出INF,
如果对s在0~2^n-1范围内,如果min=INF,则我们知道均不可修改,输出-1,
否则输出min即可*/

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值