题解 P2622 【关灯问题II】

顾z

2018-09-04 11:34:33

Solution

题目描述--->[P2622 关灯问题II](https://www.luogu.org/problemnew/show/P2622) ## 广告 [安利blog](https://rpdreamer.blog.luogu.org/#) **没用的话:** 首先第一眼看到题,嗯?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=0,1&1=1,0&0=0; 两边全部是1才为1,否则为0. -----------------介绍完毕------------------- &操作可以判断**某一位**上的灯是否开着.(注意只能是这一位. 如果你会了如何判断这个灯开着,那么关着就是**!**了 这样我们的问题就得以解决了. **设f[i]代表到达i这种状态的最少操作次数.** 如何写代码?↓ ```cpp 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].** --------------------代码-------------------- ```cpp #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得出来的 //并无什么其他意义 } ```