一点前(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\n",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\n",ans);
}

提交一下



草.jpg

状压dfs小记的更多相关文章

  1. ZOJ 1609 Equivalence(状压+dfs减枝)

    ZOJ Problem Set - 1609 Equivalence Time Limit: 5 Seconds      Memory Limit: 32768 KB When learning m ...

  2. bzoj1725: [Usaco2006 Nov]Corn Fields牧场的安排(状压dfs)

    1725: [Usaco2006 Nov]Corn Fields牧场的安排 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1122  Solved: 80 ...

  3. codeforces 285 D. Permutation Sum 状压 dfs打表

    题意: 如果有2个排列a,b,定义序列c为: c[i] = (a[i] + b[i] - 2) % n + 1 但是,明显c不一定是一个排列 现在,给出排列的长度n (1 <= n <= ...

  4. JZYZOJ1530 [haoi2013]开关控制 状压 dfs 折半搜索

    http://172.20.6.3/Problem_Show.asp?id=1530 元宵节快要到了,某城市人民公园将举办一次灯展.Dr.Kong准备设计出一个奇妙的展品,他计划将编号为1到N的N(1 ...

  5. UVALive 6255:Kingdoms(状压DFS)

    题目链接 题意 给出n个王国和n*n的矩阵,mp[i][j] 代表第 i 个王国欠第 j 个王国 mp[i][j] 块钱.如果当前的王国处于负债状态,那么这个王国就会被消除,和它相连的王国的债务都会被 ...

  6. 2018icpc南京网络赛-E AC Challenge(状压+dfs)

    题意: n道题,每道题有ai和bi,完成这道题需要先完成若干道题,完成这道题可以得到分数t*ai+bi,其中t是时间 1s, n<=20 思路: 由n的范围状压,状态最多1e6 然后dfs,注意 ...

  7. hdu 4620 Fruit Ninja Extreme(状压+dfs剪枝)

    对t进行从小到大排序(要记录ID),然后直接dfs. 剪枝的话,利用A*的思想,假设之后的全部连击也不能得到更优解. 因为要回溯,而且由于每次cut 的数目不会超过10,所以需要回溯的下标可以利用一个 ...

  8. HDU - 6341 多校4 Let Sudoku Rotate(状压dfs)

    Problem J. Let Sudoku Rotate Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K ...

  9. HDU - 6006 Engineer Assignment (状压dfs)

    题意:n个工作,m个人完成,每个工作有ci个阶段,一个人只能选择一种工作完成,可以不选,且只能完成该工作中与自身标号相同的工作阶段,问最多能完成几种工作. 分析: 1.如果一个工作中的某个工作阶段没有 ...

随机推荐

  1. jQuery制作弹出窗(模态框)

    来源:(二少)在南极 ##index.html <!DOCTYPE html><html lang="zh"><head> <meta c ...

  2. Java 获取日期间的日期 & 根据日期获取星期

    场景:根据起止日期获取中间的日期: 根据日期获取当前日期的星期 根据日期日期获取日期 /** * 获取日期间日期 * @param start * @param end * @return */ pr ...

  3. SuperMap-WebGL-坐标系及转换说明

    转载自:https://blog.csdn.net/supermapsupport/article/details/89519310 一.坐标系介绍我们先来列举下Cesium中的坐标系:WGS84经纬 ...

  4. List的Select 和Select().tolist()

    List<Person> delp = new List<Person> { ,Name=,Sign= }, ,Name= ,Sign=}, }; delp.Select(u ...

  5. 织梦DedeCMS栏目列表常见序号的调用标签

    我们在制作dedecms模板时,源代码中的[field:global name=autoindex/]标签很好用可以调用数字序号,此标签最简单的用法就是按内容条数来获取数字序号,但有的时候发现使用该标 ...

  6. Freeradius+Cisco2500AC+OpenLdap认证

    为了将公司内部认证统一化,启用了802.1x认证,认证流程如下: UserClient->AC控制器->Freeradius->OpenLdap 其中: Freeradius做认证使 ...

  7. 使用SpringBoot做Javaweb时,数据交互遇到的问题

    有段时间没做过javaweb了,有点生疏了,js也忘记得差不多,所以今天下午做前后端交互的时候,传到后台的参数总是为空,前端控制台了报一个String parameter “xxx” is not p ...

  8. 清北学堂提高组突破营游记day3

    讲课人更换成dms. 真的今天快把我们逼疯了.. 今天主攻数据结构, 基本上看完我博客能理解个大概把, 1.LCA 安利之前个人博客链接.之前自己学过QWQ. 2.st表.同上. 3.字符串哈希.同上 ...

  9. mongodb aggregate

    $project:修改输入文档的结构.可以用来重命名.增加或删除域,也可以用于创建计算结果以及嵌套文档. $match:用于过滤数据,只输出符合条件的文档.$match使用MongoDB的标准查询操作 ...

  10. POJ - 2689 Prime Distance (区间筛)

    题意:求[L,R]中差值最小和最大的相邻素数(区间长度不超过1e6). 由于非素数$n$必然能被一个不超过$\sqrt n$的素数筛掉,因此首先筛出$[1,\sqrt R]$中的全部素数,然后用这些素 ...