zoukankan      html  css  js  c++  java
  • 状压dfs小记

    一点前(tu)言(cao)

    真的考起dfs来可谓是什么都能往dfs上套
    状压不止能dp,还能与dfs结合成为搜索好(duliu)题
    剪枝卡常司空见惯(打开题解一看并不是纯dfs,emmmm)

    开始正文

    说到搜索,大家都做过八皇后对叭。现在我们让八皇后变成N皇后
    还是N皇后

    这里和八皇后不同的是地图中有些地方不可以放皇后(而且数据范围还比八皇后大1)然后它就蓝了.jpg

    我们考虑像八皇后一样直接标记列,对角线爆搜,发现会T。

    何以解T?唯有剪压(全称剪枝状压)

    我们发现(n leq 14),放在dp中这个数据一般是让你状压,于是我们可以尝试一下状压剪枝。在某个位置放皇后,会对列,左斜的对角线,右斜的对角线有影响,所以我们可以把它们分别状压成三个量,表示下一行的状态。用1表示不能放,用0表示能放。由于我们是表示的下一行的状态,所以对于左斜的对角线来说,每往下传递一行,要左移一位,右斜的对角线就要右移一位。这样很可能会导致某些1被移出棋盘。被移到棋盘右边的1在位运算里直接没了,不用管。对于被移到棋盘左边的1,我们可以拿放满的状态(即棋盘范围内全是1)和它进行and操作,消除棋盘外的1。对于每一行来说,当前行的状态就是列,左斜,右斜的三个数或起来,当然别忘了最开始输入的不可放的位置。

    我们用0表示可以放的位置,但是找到当前最低位的0似乎并没有太好的做法。这时候通过脑洞想到lowbit可以找到当前二进制表示中最低位的1。于是我们可以进行取反操作,然后对取反后的数不断取它的lowbit值来找到当前可以放皇后的位置。

    同时我们还要状压一下哪些行可以放皇后,如果能放满,就说明这是个合法的方案。

    由于位运算常数小,所以在我们没有剪枝的情况下也是可以轻松AC这道题的。

    Code

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #define pa pair<int,int>
    using namespace std;
    typedef long long ll;
    inline ll read()
    {
    	char ch=getchar();
    	ll x=0;bool f=0;
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-')f=1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<3)+(x<<1)+(ch^48);
    		ch=getchar();
    	}
    	return f?-x:x;
    }
    ll n,m,ok[19],k,ans,en;//ok[i]表示输入的地图中第i行的状态,en就是每行都放满的状态
    char mp[19][19];
    ll lowbit(int x)
    {
        return x&(-x);
    }
    void dfs(int now,ll lie,ll x1,ll x2,ll hang)//x1是左斜对角线,x2是右斜对角线,now记录dfs到了第几行,hang记录哪些行放了皇后
    {
       if(hang==en){ans++;return ;}
       ll kk=~(ok[now]|x1|x2|lie);
       kk&=en;
       while(kk)
       {
           ll v=lowbit(kk);
           kk-=v;
           dfs(now+1,lie|v,(x1|v)<<1,(x2|v)>>1,hang|(1<<now));//注意更新在当前列放皇后的影响
       }
    }
    int main()
    {
       n=read();
       for(int i=1;i<=n;i++)
        scanf("%s",mp[i]+1);
       for(int i=1;i<=n;i++)
       {
           for(int j=1;j<=n;j++)
            if(mp[i][j]=='.')
                ok[i]|=(1<<j);
       }
       en=(1<<n+1)-1;
       en^=1;//在这里2^1表示第一位,而不是2^0
       dfs(1,0,0,0,0);
       printf("%lld
    ",ans);  
    }
    

    状压+dfs不止能解决皇后问题,还能解决数独问题。
    靶形数独



    咱也不造为啥状压完了不用剪枝就能过.jpg
    显然我们需要先处理每个九宫格的表示方法和每个格子的分数,由于我太菜了,所以打了个表然后进行(O(1))询问即可

    打的表:

    int gt[15][15]=//分数
    { {0,0,0,0,0,0,0,0,0,0},
      {0,6,6,6,6,6,6,6,6,6},
      {0,6,7,7,7,7,7,7,7,6},
      {0,6,7,8,8,8,8,8,7,6},
      {0,6,7,8,9,9,9,8,7,6},
      {0,6,7,8,9,10,9,8,7,6},
      {0,6,7,8,9,9,9,8,7,6},
      {0,6,7,8,8,8,8,8,7,6},
      {0,6,7,7,7,7,7,7,7,6},
      {0,6,6,6,6,6,6,6,6,6},
    },
    gz[15][15]=//每个格子
    { {0,0,0,0,0,0,0,0,0,0},
      {0,1,1,1,2,2,2,3,3,3},
      {0,1,1,1,2,2,2,3,3,3},
      {0,1,1,1,2,2,2,3,3,3},
      {0,4,4,4,5,5,5,6,6,6},
      {0,4,4,4,5,5,5,6,6,6},
      {0,4,4,4,5,5,5,6,6,6},
      {0,7,7,7,8,8,8,9,9,9},
      {0,7,7,7,8,8,8,9,9,9},
      {0,7,7,7,8,8,8,9,9,9},
    };
    

    各位大佬如果有公式求告知

    我们先考虑如果人类玩数独应该怎么玩。显然我们要选一个确定的数比较多的地方开始搜索。在这里为了调试的时候比较优雅我只会从行搜,我们选取已知数字较多的行开始搜索。

    我们先参照上一个题的思路,考虑对行,列,九宫格进行状压。wait!对九宫格进行状压!压个(peach)

    所以我们就这么干脆的放弃了全部状压。其实对列,九宫格状压没有什么意义,只要标记一下哪一些填了哪些数就可以了。bool数组肯定是开的下的。那么我们考虑对每一行中填了哪些数进行状压,以便快速的找到第一个可以填数的位置。状压方法和上一个题相同,也是用0表示可以填的位置,1表示不可以填的位置,询问的时候取反+lowbit即可。

    判断无解:在输入的时候先统计好确定的数能拿到的总分数,如果最终分数没变,即无解

    由于这题玄学的数据和我也不会算的复杂度,我们可以以最高666ms/一个数据点的速度AC掉这道题

    具体看代码叭(博主语文能力有限无法详细表达.jpg

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    inline ll read()
    {
    	char ch=getchar();
    	ll x=0;bool f=0;
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') f=1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<3)+(x<<1)+(ch^48);
    		ch=getchar();
    	}
    	return f?-x:x;
    }//被某些毒瘤题卡掉过的快读
    int mp[10][10],ans,jg[10][10],hh[15];//mp为初始的数独,jg记录每个九宫格里面哪个数被用过了
    struct ifm{
    	int id,wz,lz;//wz记录信息完整度(也就是有多少个非零数),lz记录状压之后的数
    }hang[15],lie[15];//在博主一番调(mo)试(gai)之后lie没有用了
    int cpy[15][15],wwz;//wwz记录有多少个位置不是0,cpy在博主的一番调(mo)试(gai)之后也没有用了
    bool bjh[15][10],bjl[15][10];//bjh[i][j]表示在第i行里,j这个数是否使用过,bjl是记录列
    int gt[15][15]=
    { {0,0,0,0,0,0,0,0,0,0},
      {0,6,6,6,6,6,6,6,6,6},
      {0,6,7,7,7,7,7,7,7,6},
      {0,6,7,8,8,8,8,8,7,6},
      {0,6,7,8,9,9,9,8,7,6},
      {0,6,7,8,9,10,9,8,7,6},
      {0,6,7,8,9,9,9,8,7,6},
      {0,6,7,8,8,8,8,8,7,6},
      {0,6,7,7,7,7,7,7,7,6},
      {0,6,6,6,6,6,6,6,6,6},
    },
    gz[15][15]=
    { {0,0,0,0,0,0,0,0,0,0},
      {0,1,1,1,2,2,2,3,3,3},
      {0,1,1,1,2,2,2,3,3,3},
      {0,1,1,1,2,2,2,3,3,3},
      {0,4,4,4,5,5,5,6,6,6},
      {0,4,4,4,5,5,5,6,6,6},
      {0,4,4,4,5,5,5,6,6,6},
      {0,7,7,7,8,8,8,9,9,9},
      {0,7,7,7,8,8,8,9,9,9},
      {0,7,7,7,8,8,8,9,9,9},
    };//上面打好的表
    int er[1029];//er[2^x]=x,在lowbit之后算出是第几列
    bool cmp(ifm a,ifm b)
    {
    	return a.wz>b.wz;
    }
    int lowbit(int x)
    {
    	return x&(-x);
    } 
    void dfs(int ha,int ann,int now)//ha表示当前填第几行,ann表示当前的分数,now表示这是在排序后的hang里的第几个
    {
    	if(wwz==81)
    	{
    		ans=max(ans,ann);
    		return;
    	}
    	if(hh[ha]!=1022)//由于窝习惯用2^1表示第一个数,所以是1023-1=1022
    	{
    		int kkk=~hh[ha];
    		kkk^=1;
    		int v=lowbit(kkk);
    		for(int i=1;i<=9;i++)
    		{
    			if(!bjh[ha][i]&&!bjl[er[v]][i]&&!jg[gz[ha][er[v]]][i])//判断能否填数
    			{
    				bjh[ha][i]=1;
    				hh[ha]|=v;
    				cpy[ha][er[v]]=i;	
    				bjl[er[v]][i]=1;	
    				jg[gz[ha][er[v]]][i]=1;//一堆神烦的标记
    				if(hh[ha]!=1023)//如果填了之后,这一行还没填满,就去填这一行的下一个数 
    				{
    					wwz++;
    					dfs(ha,ann+i*gt[ha][er[v]],now);
    				}
    				else wwz++,dfs(hang[now+1].id,ann+i*gt[ha][er[v]],now);//这一行填满了,搜下一个要搜的行
    				bjl[er[v]][i]=0;
    				jg[gz[ha][er[v]]][i]=0;
    				hh[ha]^=v;
    				bjh[ha][i]=0;
    				wwz--;
    				cpy[ha][er[v]]=0;
    			}
    		}
    	}
    	else//如果当前行已经填满了,就填下一个
    	 dfs(hang[now+1].id,ann,now+1);
    }
    int main()
    {
    	er[1]=0;
    	for(int i=1;i<=9;i++)
    	  er[1<<i]=i;  
        for(int i=1;i<=9;i++)
         for(int j=1;j<=9;j++)
          {
          	mp[i][j]=read();
    		if(mp[i][j])
          	{
          		hang[i].wz++;
          		lie[j].wz++;
          		hang[i].lz|=(1<<j);
          		lie[j].lz|=(1<<i);//应该没有用了叭
          		jg[gz[i][j]][mp[i][j]]=1;
          		ans+=mp[i][j]*gt[i][j];
          		wwz++;
          		bjh[i][mp[i][j]]=1;
          		bjl[j][mp[i][j]]=1;
    		}
    	  }  
    	for(int i=1;i<=9;i++)
    	 hang[i].id=i,lie[i].id=i;  
        for(int i=1;i<=9;i++)
          hh[i]=hang[i].lz;
    	sort(hang+1,hang+10,cmp);//保证先搜已知数字多的行
    	sort(lie+1,lie+10,cmp);//大概没有用了叭
    	int qwq=ans;
    	dfs(hang[1].id,ans,1);
    	if(ans==qwq) ans=-1;//判断无解
    	printf("%d
    ",ans);
    }
    

    提交一下

    草.jpg

  • 相关阅读:
    学习笔记1:性能问题在于你想不到的任何地方!
    React Native控件之PullToRefreshViewAndroid下拉刷新组件解说(20)
    CSDN-markdown编辑器之导入导出文档功能
    无名管道与有名管道通讯编程
    LeetCode Remove Nth Node From End of List
    libpcap出错信息调试函数pcap_geterr, pcap_perror
    工作流学习——Activiti流程实例、任务管理四步曲
    B
    oracle学习笔记 oracle软件安装和数据库的创建
    路由器逆向分析------MIPS系统网络的配置(QEMU)
  • 原文地址:https://www.cnblogs.com/lcez56jsy/p/11818540.html
Copyright © 2011-2022 走看看