1. 程式人生 > >p2622 關燈問題II

p2622 關燈問題II

bits reg con 如何判斷 .org class its 是我 分析

題目描述--->P2622 關燈問題II

沒用的話:

首先第一眼看到題,嗯?n<=10?搜索?

滿心歡喜地敲了一通搜索。

交上去,Wa聲一片?

全部MLE! 這麽坑人神奇?

一想,可能是爆棧了 emmm

思考了一番看了下題解&&標簽

哦原來是狀壓DP

情不自禁地分析

n<=10.

最多只有(2<<10)-1=1023種狀態

我們完全可以用數組存儲狀態.

以樣例為例: 算了 太辣雞了,我自己瞎出吧.

如果n=8,即一共有8盞燈.

初始狀態它們全開著,我們可以認為是這樣的↓
1 1 1 1 1 1 1 1 
我們用十進制數(1<<8)-1=255存儲這一狀態.

此時,如果有一個按鈕可以關掉第7個燈(從右向左數).

我們如何做到這一操作?↓

你應該會想到通過位運算操作.
那我們應該 1<< ?才能對應上第七個燈呢?

不妨嘗試一下1<<7 是這樣↓
1 0 0 0 0 0 0 0 
1 1 1 1 1 1 1 1 //初始狀態
我們發現:哇!到了第8個燈!

所以說我們應該1<<6 得到這樣↓
0 1 0 0 0 0 0 0
1 1 1 1 1 1 1 1
這時,我們就可以控制第7個燈了!

你會不會有疑問?(好吧,我強加的

為什麽算狀態的時候是(1<<n)-1?

而算第i個燈的時候是1<<(i-1)個?

‘<<‘操作是我們把1進行平移(我思想中的平移

演示一下這個過程.

  0000000001   //某一個2進制下的數
  0000000010   //原數<<1
  0000000100   //原數<<2
  .
  .
  以此類推.

我們發現,1<<i,我們的1後面就會有i個0

如果1<<n位(這裏以8為例)

得到1 0 0 0 0 0 0 0 0
數一下 這一共是9個燈.
-1之後,我們得到這樣的東西↓
    0 1 1 1 1 1 1 1 1
 //這表示一共有8個燈,全部開著.

所以想要計算這是哪一種狀態,我們就可以(1<<n)-1了

這樣疑問就解決了//如果不理解再回去看看,動手試試


題目三種操作.

如果a[i][j]為1,那麽當這盞燈開了的時候,把它關上,否則不管;如果為-1的話,如果這盞燈是關的,那麽把它打開,否則也不管;如果是0,無論這燈是否開,都不管。

首先請看清上面三種操作,再向下看

對於操作1,我們難點就在於判斷當前狀態下這盞燈是否開著.

如果你懂得&操作,那就很簡單了!

-----------------介紹一下&---------------

按位與操作,1&0=1,1&1=1,0&0=0;

兩邊全部是1才為1,否則為0.

-----------------介紹完畢-------------------

&操作可以判斷某一位上的燈是否開著.(註意只能是這一位.

如果你會了如何判斷這個燈開著,那麽關著就是!

這樣我們的問題就得以解決了.

設f[i]代表到達i這種狀態的最少操作次數.

如何寫代碼?↓

for(RI i=(1<<n)-1;i>=0;i--)//枚舉狀態
    {
        for(RI j=1;j<=m;j++)//枚舉開關
        {
            int now=i;//我們的now最終會變為,按完這個開關後的狀態.
            for(RI l=1;l<=n;l++)//枚舉控制的燈.
            {
                if(a[j][l]==0)continue;//不操作
                if(a[j][l]==1 and (i&(1<<(l-1)))) now^=(1<<(l-1));//第一個操作
                if(a[j][l]==-1 and !(i&(1<<(l-1)))) now^=(1<<(l-1));
                //第二個操作
            }
            f[now]=std::min(f[now],f[i]+1);//常規操作,求最小操作次數.
            //因為我們可以通過i狀態操作按一個開關到達now狀態.
            //所以是f[i]+1.
        }
    }

嘗試解釋一下為什麽第一層是枚舉狀態.

我們的開關可以控制所有狀態來得到下一個狀態.

而且我們的初始狀態是全部開著,需要倒著枚舉.直到為0(全部關閉).

所以我們的答案就是f[0].

--------------------代碼--------------------

#include<bits/stdc++.h>
#define IL inline
#define RI register int
IL void in(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<‘0‘||s>‘9‘){if(s==‘-‘)f=-1;s=getchar();}
    while(s>=‘0‘&&s<=‘9‘){x=x*10+s-‘0‘;s=getchar();}
    x*=f;
}
int a[108][18],f[2048],n,m;
int main()
{
    in(n),in(m);
    memset(f,0x3f,sizeof f);
    for(RI i=1;i<=m;i++)
        for(RI j=1;j<=n;j++)
            in(a[i][j]);
    f[(1<<n)-1]=0;//燈全開著的操作次數為0
    //printf("%d",(1<<n)-1); 
    for(RI i=(1<<n)-1;i>=0;i--)
    {
        for(RI j=1;j<=m;j++)
        {
            int now=i;
            for(RI l=1;l<=n;l++)
            {
                if(a[j][l]==0)continue;
                if(a[j][l]==1 and (i&(1<<(l-1)))) now^=(1<<(l-1));
                if(a[j][l]==-1 and !(i&(1<<(l-1)))) now^=(1<<(l-1));
            }
            f[now]=std::min(f[now],f[i]+1);
        }
    }
    printf("%d",f[0]==1061109567?-1:f[0]);
    //這個奇怪的數字就是memset 0x3f得出來的
    //並無什麽其他意義
}

p2622 關燈問題II