zoukankan      html  css  js  c++  java
  • 状压DP【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得出来的
        //并无什么其他意义
    }
    
    除特殊声明外,本博客作品均由顾z创作。 未经博主允许,不得转载
  • 相关阅读:
    bzoj1625 / P2871 [USACO07DEC]手链Charm Bracelet
    bzoj1623 / P2909 [USACO08OPEN]牛的车Cow Cars
    bzoj1622 / P2908 [USACO08OPEN]文字的力量Word Power
    bzoj1621 / P2907 [USACO08OPEN]农场周围的道路Roads Around The Farm
    bzoj1620 / P2920 [USACO08NOV]时间管理Time Management
    [3.10校内训练赛]
    [bzoj1084][SCOI2005]最大子矩阵
    [bzoj1500][NOI2005]维修数列
    bzoj省选十连测推广赛
    多项式插值学习记录
  • 原文地址:https://www.cnblogs.com/-guz/p/9613035.html
Copyright © 2011-2022 走看看