UVa11464-Even Parity开关问题


题目链接:点击这里

初步思路

第一次看见这种问题,没有太多思路,就想直接枚举+剪枝。但如何剪枝呢?首先,如果直接枚举所有的情况,很明显是不可能的,若为 1515 15 ∗ 15 的矩阵,则需要枚举 21515 2 15 ∗ 15 种方法,实在是太大。因此剪枝成为了必要选择。可以想到,若我们已经确定了第一排的所有元素,则第二排也已经被确定了–因为第二排的元素将决定第一排的每个元素是否符合要求。这样,第二排元素也会确定第三排元素,直到所有元素都确定。

思路总结

经过Google,此种类型的问题被称为“开关问题”,就像多米诺骨牌一样,前面的会影响后面的,因此只要枚举最前面的所有可能情况即可,这样,此问题的复杂度被降到 O(2nn2) O ( 2 n ∗ n 2 ) ,由于此题目 n n 最多等于15,因此可以接受。

代码实现

此题的代码技巧性比较强,耐心看了好久才看懂。

技巧

  1. 此题需要对第一排的每个01元素进行枚举,可使用数的二进制形式来模拟枚举,降低运算量。
  2. “&”运算来表示奇偶性。
  3. 多使用<< >> 移位运算符来提高速度,也更直观。
  4. count的次数并不需要挨个计算,将最后算出的矩阵与原始矩阵进行对比即可。

注意点

此题只能将0变为1,不能将1变为0,这是判断条件。

代码实现

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 20;
const int INF = 1<<30;

int dx[] = {0,0,-1};
int dy[] = {-1,1,0};

int a[N][N];
int tmp[N][N];

int ans;


int getSum(int x, int y, int n)
{
    int ret = 0;
    for(int i = 0; i < 3; i++)
    {
        /*只考虑前后上三个方向,因为是从前往后扫描,
        下方向的可以根据前面的和进行更改*/
        int tx = x + dx[i];
        int ty = y + dy[i];
        if(tx >= 1 && tx <= n && ty >= 1 && ty <= n)
            ret += tmp[tx][ty];
    }
    return ret;
}

int check(int s, int n)
{
    //先计算第一行
    for(int i = 1; i <= n; i++)
    {
        //将s的二进制形式写入矩阵中
        tmp[1][i] = s&1;
        if(tmp[1][i] == 0 && a[1][i] == 1)
            //若只能将1变为0才满足,则不合题意
            return INF;
        //移向下一位
        s>>=1;
    }
    //依次计算后面的
    for(int i = 2; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            int ret = getSum(i-1,j,n);
            //计算前一行中三个方向的总和,若已经为偶数,则当前元素填0
            tmp[i][j] = ret & 1;
            if(tmp[i][j] == 0 && a[i][j] == 1)
                //若只能将1变为0才满足,则不合题意
                return INF;
        }
    }
    int cnt = 0;
    for(int i =1 ; i <= n; i++)
        for(int j = 1; j <= n; j++)
            //通过比较是否相同来计算次数
            cnt += (a[i][j] != tmp[i][j]);
    return cnt;
}

int main()
{
    int t,n;
    cin>>t;
    for(int kcase = 1; kcase <= t; kcase++)
    {
        cin>>n;
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=n; ++j)
            {
                cin>>a[i][j];
            }
        }
        ans = INF;
        for(int s=0; s<=(1<<n)-1; s++)
        {
            ans = min(ans,check(s,n));
        }
        if(ans == INF)
            ans = -1;
        cout<<"Case "<<kcase<<": "<<ans<<endl;
    }
    return 0;
}

最后,欢迎关注我的个人博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值