p2622 關燈問題II
題目描述--->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