poj2965--較為巧妙的算法


這一題大意如下:

      一個冰箱上有4*4共16個開關,改變任意一個開關的狀態(即開變成關,關變成開)時,此開關的同一行、同一列所有的開關都會自動改變狀態。要想打開冰箱,要所有開關全部打開才行。

     輸入:一個4×4的矩陣,+表示關閉,-表示打開;

   輸出:使冰箱打開所需要執行的最少操作次數,以及所操作的開關坐標。

這一題很多人使用BFS的方法,其實還有一種更為精妙的方法,值得大家思考。

這種方法網上很多人都提到了,但是說的都不太清晰,我想了很久也沒想明白,后來我用程序追蹤一下運行的過程,才豁然開朗。

首先要明白最基本的原理:對一個開關進行操作n次,如果n為偶數,那么這個開關以及同行、同列的開關狀態都不發生改變,等價於沒有操作;如果n為奇數,那么這個開關以及同行同列的開關狀態全都發生改變,等價於只操作了一次。

要想使所有開關狀態全部打開(全部是-),就要把所有+變成-,所有-不改變。我們要做的就是找到一種“公式”,策略,使得不改變已經打開的開關狀態的情況下,把關閉的開關打開。這點很類似於魔方(PS:玩過魔方的都知道,魔方所謂的公式,其實就是在不改變已經拼好的部分的情況下,把其他部分一點一點添加到已拼好的部分)。

我們找到的策略就是:把開關本身以及其同一行同一列的開關(總共7個)都進行一次操作,結果是,開關本身狀態改變了7次,開關同一行、同一列的開關狀態改變了4次,其他開關狀態改變了2次。如下圖所示。

假如開關坐標為第二行第三列的(2,3),那么按照上述策略(把開關本身以及其同一行同一列的開關都進行一次操作),結果分析如下:

對於黃色部分的開關,只有與此黃色開關同一行和同一列的兩個紅色開關操作時,此黃色開關的狀態才會發生改變,因此所有黃色部分狀態改變次數為2,相當於0次

對於紅色部分的開關,只有與此紅色開關同一列或同一列的開關操作時,此紅色開關狀態才會發生改變,一行或者一列有4個開關,因此紅色部分開關狀態改變次數為4,相當於0次

對於最原始的那個黑色開關,所有紅色開關操作時,它的狀態改變一次,然后黑色開關自己操作一次,因此黑色開關狀態改變7次,相當於改變1次。

總結上述分析可以得出結論,把開關本身以及其同一行同一列的開關都進行一次操作,最終結果是只有開關本身狀態發生變化,其他所有開關狀態都不變。

 

策略找到之后,那我們就想,如果對於所有關閉着的開關都進行一次上述策略,那么肯定是能把冰箱打開的,下面我們要做的就是把一些無用的,重復的操作去掉即可。

用一個4*4的數組記錄每個開關操作的次數,初始化為0,開關操作一次,記錄就+1,以樣例(http://poj.org/problem?id=2965)為例:

-+--
----
----
-+--


1 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0


1 1 0 0
0 0 0 0
0 0 0 0
0 0 0 0


1 1 1 0
0 0 0 0
0 0 0 0
0 0 0 0


1 1 1 1
0 0 0 0
0 0 0 0
0 0 0 0


1 1 1 1
0 1 0 0
0 0 0 0
0 0 0 0


1 1 1 1
0 1 0 0
0 1 0 0
0 0 0 0


1 1 1 1
0 1 0 0
0 1 0 0
0 1 0 0

 

 

1 1 1 1
0 1 0 0
0 1 0 0
1 1 0 0


1 1 1 1
0 1 0 0
0 1 0 0
1 2 0 0


1 1 1 1
0 1 0 0
0 1 0 0
1 2 1 0


1 1 1 1
0 1 0 0
0 1 0 0
1 2 1 1


1 2 1 1
0 1 0 0
0 1 0 0
1 2 1 1


1 2 1 1
0 2 0 0
0 1 0 0
1 2 1 1


1 2 1 1
0 2 0 0
0 2 0 0
1 2 1 1

對於樣例中的每一個+開關,進行一次策略,記錄數組所記錄的每一個開關操作的次數變化如上所示。那么在最終得到的數組中可以看出,有些開關操作了偶數次,有些操作了奇數次。操作了偶數次的開關就是上面所說的無用的,重復的操作,直接去掉,留下奇數次的就最終的答案。

代碼寫的不好,僅供參考

#include<stdio.h>
#include<malloc.h>

char handle[4][4];  //存儲字符
int record[4][4];   //記錄每一位操作次數

void set_handle()
{
    int i, j;
    for(i = 0; i < 4; i++)
    {
        scanf("%s", handle[i]);
    }
    memset(record, 0, sizeof(record));
}

char change_state(char state)
{
    if(state == '+')
        return '-';
    return '+';
}

//把m行n列的開關按下
void flip(int m, int n)
{
    int i, j;
    for(j = 0; j < 4; j++)  //行操作
    {
        handle[m][j] = change_state(handle[m][j]);
    }
    for(i = 0; i < 4; i++)  //列操作
    {
        if(i != m)
          handle[i][n] = change_state(handle[i][n]);
    }
    record[m][n]++; //執行一次操作
}

//對數組中所有+位置的行和列所有的開關執行一次flip操作
void full_flip()
{
    int i, j;
    for(i = 0; i < 4; i++)
    {
        for(j = 0; j < 4; j++)
        {
            if(handle[i][j] == '+')
            {
                int t;
                for(t = 0; t < 4; t++)
                {
                    flip(i, t);
                }

                for(t = 0; t < 4; t++)
                {
                    if(t != i)
                    {
                       flip(t,j);
                    }

                }
            }
        }
    }
}


//遍歷record數組,統計操作次數
void solute()
{
    int count = 0;
    int i, j;
    for(i = 0; i < 4; i++)
    {
        for(j = 0; j < 4; j++)
        {
            if(record[i][j] & 1  != 0)
            {
                count ++;
            }
        }
    }
    printf("%d\n", count);
    for(i = 0; i < 4; i++)
    {
        for(j = 0; j < 4; j++)
        {
            if(record[i][j] & 1  != 0)
            {
                printf("%d %d\n", i+1, j+1);
            }
        }
    }

}

int main(void)
{
    set_handle();
    full_flip();
    solute();
    return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM