状压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\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小记的更多相关文章
- ZOJ 1609 Equivalence(状压+dfs减枝)
ZOJ Problem Set - 1609 Equivalence Time Limit: 5 Seconds Memory Limit: 32768 KB When learning m ...
- bzoj1725: [Usaco2006 Nov]Corn Fields牧场的安排(状压dfs)
1725: [Usaco2006 Nov]Corn Fields牧场的安排 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1122 Solved: 80 ...
- codeforces 285 D. Permutation Sum 状压 dfs打表
题意: 如果有2个排列a,b,定义序列c为: c[i] = (a[i] + b[i] - 2) % n + 1 但是,明显c不一定是一个排列 现在,给出排列的长度n (1 <= n <= ...
- JZYZOJ1530 [haoi2013]开关控制 状压 dfs 折半搜索
http://172.20.6.3/Problem_Show.asp?id=1530 元宵节快要到了,某城市人民公园将举办一次灯展.Dr.Kong准备设计出一个奇妙的展品,他计划将编号为1到N的N(1 ...
- UVALive 6255:Kingdoms(状压DFS)
题目链接 题意 给出n个王国和n*n的矩阵,mp[i][j] 代表第 i 个王国欠第 j 个王国 mp[i][j] 块钱.如果当前的王国处于负债状态,那么这个王国就会被消除,和它相连的王国的债务都会被 ...
- 2018icpc南京网络赛-E AC Challenge(状压+dfs)
题意: n道题,每道题有ai和bi,完成这道题需要先完成若干道题,完成这道题可以得到分数t*ai+bi,其中t是时间 1s, n<=20 思路: 由n的范围状压,状态最多1e6 然后dfs,注意 ...
- hdu 4620 Fruit Ninja Extreme(状压+dfs剪枝)
对t进行从小到大排序(要记录ID),然后直接dfs. 剪枝的话,利用A*的思想,假设之后的全部连击也不能得到更优解. 因为要回溯,而且由于每次cut 的数目不会超过10,所以需要回溯的下标可以利用一个 ...
- HDU - 6341 多校4 Let Sudoku Rotate(状压dfs)
Problem J. Let Sudoku Rotate Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K ...
- HDU - 6006 Engineer Assignment (状压dfs)
题意:n个工作,m个人完成,每个工作有ci个阶段,一个人只能选择一种工作完成,可以不选,且只能完成该工作中与自身标号相同的工作阶段,问最多能完成几种工作. 分析: 1.如果一个工作中的某个工作阶段没有 ...
随机推荐
- mongodb的简单操作记录
由于项目上需要对mongodb进行监控,所以需要先熟悉下什么是mongobd以及mongodb的简单操作 mongodb的安装: curl -O https://fastdl.mongodb.org/ ...
- Hybrid APP架构设计
通讯 作为一种跨语言开发模式,通讯层是Hybrid架构首先应该考虑和设计的,往后所有的逻辑都是基于通讯层展开. Native(以Android为例)和H5通讯,基本原理: Android调用H5:通过 ...
- qq游戏IE组件停止工作
你可以下载一个腾讯电脑管家,利用电脑诊所里的腾讯游戏专区里的“网页游 游戏玩不了”这一项修复一下即可.我遇见一次,修复之后就解决了.个人认为是Adobe Flash出问题了.祝你玩的开心.
- JS常用函数原理的实现
本文针对目前常见的面试题,实现了相应方法的核心原理,部分边界细节未处理.后续也会持续更新,希望对你有所帮助. 1.实现一个call函数 // 思路:将要改变this指向的方法挂到目标this上执行并返 ...
- mysql的auto-rehash简介
今天在看mysql的配置文件的时候,发现有"auto-rehash"不明白什么意思,在此记录一下,auto-rehash是自动补全的意思,就像我们在linux命令行里输入命令的时候 ...
- python中的定时任务
使用threading模块中的Timer函数 from threading import Timer import time def execute_func(name, age, gender, h ...
- typedef 返回类型(*Function)(参数表) ——typedef函数指针
//首先看一下函数指针怎么用 #include <iostream> using namespace std; //定义一个函数指针pFUN,它指向一个返回类型为char,有一个整型的参数 ...
- 从subversion开始(svn安装配置全过程(+全套安装文件与配置文件))…..
从subversion开始(svn安装配置全过程(+全套安装文件与配置文件))-.. 博客分类: 工具使用 SVNsubversion配置管理Apache应用服务器 </div> 花了一 ...
- 咕qwq
ccsp回来之后一直肚子难受,到现在还没好. 下午去人民医院急诊做了个CT,医生说是有问题的,但她看不出来,让我明天早起挂专家号去QAQ. UPD:初步诊断是胀气.医生让我先吃两天抗生素...
- Backtracking(一)
LeetCode中涉及到回溯的题目有通用的解题套路: 46. permutations 这一类回溯题目中的基础中的基础,无重复数字枚举: /* Given a collection of distin ...