• POJ 3279 Fliptile (二进制枚举)


     <题目链接>

    <转载于 >>> >

    题目大意:
     给定一个M*N矩阵,有些是黑色(1表示)否则白色(0表示),每翻转一个(i,j),会使得它和它周围4个格变为另一个颜色,要求翻转最少的点,使得变为全白色的矩阵,输出这个标记了翻转点的矩阵,如果有多个最优解,输出字典序最小的那个矩阵,若没有解,输出IMPOSSIBLE。

    解题分析:

    由于一个点翻转两次则返回原来的状态,所以最优解每个点最多翻转一次,但是2^(M*N)过大,所以2^N枚举第一行的所有翻转方式(逆字典序枚举),确定一种方式之后第二行也就随之确定了,因为如果第一行处理后没有翻回白色的点:(i,j),必须在第二行(i+1,j)翻回,否则将无法返回。反之第二行其他的点都处理为不翻转,要不然上一行的点会翻回黑色而无法改变。第二行ok后同理解决第三行,以此类推。处理到最后一行如果不是全白就输出IMPOSSIBLE。否则更新结果。

    即,用二进制枚举第一行的翻转情况,然后2~n-1行按照上一行的情况来翻转,最后再判断最后一行是否全部为0,如果为0,则记录下翻转次数,随时更新答案。

     
    #include<cstdio>
    #include<cstring>
    int t[30][30], tem[30][30], m[30][30];
    //这里用一个数组记录翻转次数,再配合原来的点数,就能判断反转后的点数,这里很巧妙
    int M,N,dir[5][2] = { 0,0,1,0,0,1,-1,0,0,-1 };
    
    int get(int x, int y)//获得x,y点的颜色       //它本身的点数,再加上周围四个点反转的次数,就能得到它的真实点数
    {
        int c = t[x][y];
        for (int i = 0; i < 5; i++)
        {
            int x1 = x + dir[i][0], y1 = y + dir[i][1];
            c += tem[x1][y1];
        }
        return c % 2;
    }
    
    int cal()  //计算2行及之后的,有解返回翻点数,无解返回-1
    {
        for (int i = 2; i <= M; i++)
            for (int j = 1; j <= N; j++)
                if (get(i - 1, j) == 1)
                    tem[i][j] = 1;
        //得到前n-1行的翻转次数
    
        for (int i = 1; i <= N; i++)
            if (get(M, i))return -1;      //如果最后一行有一个点不为0,说明枚举的第一行不符合要求s
        int res = 0;
        for (int i = 1; i <= M; i++)
            for (int j = 1; j <= N; j++)
                res += tem[i][j];
        return res;    //记录下需要翻转的总次数
    }
    
    int main()
    {
        int min = -1;   //次数>0可以这样初始化
        scanf("%d%d", &M, &N);
        for (int i = 1; i <= M; i++)
            for (int j = 1; j <= N; j++)
                scanf("%d", &t[i][j]);
        for (int i = 0; i < (1 << N); i++)    //枚举第一行的所有情况
        {
            memset(tem, 0, sizeof(tem));    //初始化翻转数组
            for (int j = 1; j <= N; j++)
                tem[1][j] = (i >> (j - 1)) & 1;     //根据二进制得到第一行的翻转情况,这个技巧一定要掌握
    
            int num = cal();
            if (num >= 0 && (min<0 || min>num))    //取情况成立并且总翻转次数最小的
            {
                min = num;
                memcpy(m, tem, sizeof(tem));      //记录下最后的翻转矩阵
            }
        }
    
        if (min == -1)printf("IMPOSSIBLE
    ");
        else
        {
            for (int i = 1; i <= M; i++)
                for (int j = 1; j <= N; j++)
                    printf("%d%c", m[i][j], j == N ? '
    ' : ' ');
        }
        return 0;
    }

    2018-08-30

  • 相关阅读:
    awk查看本机IP+查看本网段mac
    wget
    kvm安装,使用,优化
    方向
    bindview+dlz(mysql)
    页面侧滑栏效果
    数据库的事务理解
    页面平衡移动进出的效果
    返回键弹出是否退出对话框
    引导页之页面等待3秒跳转
  • 原文地址:https://www.cnblogs.com/00isok/p/9560386.html
Copyright © 2020-2023  润新知