按照一定的顺序和规则,一直往深处走,直到走不通再返回,换一种路径重复上述步骤。
深搜一般可以找到问题的所有答案,但问题规模较大时,解集树的深度就会比较大并且比较宽,时间复杂度就会较高。与广搜相比,深搜的空间复杂度会较低,因为深搜是深度优先,只需要存储当前子树的状态,不需要像广搜那样存储大量状态。
1、可行性剪枝和最优性剪枝
2、记忆化搜索
3、减少重复搜索
P1451 求细胞数量
深搜的入门题,求出连通块,把搜索过的地方做标记,记录连通块数量即可
#include
#define eps 1e-15
using namespace std;
int n,m,a[105][105],r[4]={-1,1,0,0},c[4]={0,0,-1,1};
void dfs(int i, int j){
a[i][j]=0;
for(int k=0; k<4; k++){
int b=i+r[k],d=j+c[k];
if(a[b][d] != 0 && 1<=b && b<=n && d>=1 && d<=m)dfs(b,d);
}
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
scanf("%1d",&a[i][j]);
int ans=0;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++){
if(a[i][j]!=0)dfs(i,j),ans++;
}
cout<<ans;
return 0;
}
P1162 填涂颜色
也是求连通块的题,如果找圈内的块,不知道具体在哪,不好去控制深搜程序。如果找出圈外的连通块,把圈外的涂色,就能区别出圈内的。
#include
#define eps 1e-15
using namespace std;
int n,a[35][35],b[35][35],x[4]={-1,1,0,0},y[4]={0,0,-1,1};
void dfs(int r, int c){
if(b[r][c]==1 || b[r][c]==2)return;
b[r][c]=2;
for(int k=0; k<4; k++){
int rr=r+x[k],cc=c+y[k];
if(rr>=1 && rr<=n && cc>=1 && cc<=n)dfs(rr,cc);
}
}
/*
6
1 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
*/
int main()
{
cin>>n;
for(int i=1 ;i<=n; i++)
for(int j=1; j<=n; j++){
cin>>a[i][j];
b[i][j]=a[i][j];
}
//这样能保证找出圈外的所有连通块
for(int i=1; i<=n; i++)
dfs(1,i);
for(int i=1; i<=n; i++)
dfs(i,1);
for(int i=1; i<=n; i++)
dfs(n,i);
for(int i=1; i<=n; i++)
dfs(i,n);
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++)
if(1==b[i][j])cout<<1<<" ";
else if(b[i][j]==2)cout<<0<<" ";
else if(b[i][j]==0)cout<<2<<" ";
cout<<endl;
}
return 0;
}
P1141 01迷宫
还是连通块的题,但是技巧巧妙。
每次搜索都能找出一个连通块,连通块内能到达的数量都是一样的。
flag数组用来存答案数组的下标,ans数组用来存答案。
还需要注意搜索完后恢复a数组的状态。不能在每一层深搜结束后把当前的答案直接当成这个点的最终答案,因为这样不能保证这个连通块的每个值都是相同的。
#include
#define eps 1e-15
using namespace std;
int n,a[1005][1005],flag[1005][1005],r[4]={-1,1,0,0},c[4]={0,0,-1,1},ans[1000005];
void dfs(int i, int j,int l){
if(!flag[i][j]){ans[l]++;flag[i][j]=l;}
int x=a[i][j];
a[i][j]=-1;
for(int k=0; k<4; k++){
int b=i+r[k], d=j+c[k];
if(b>=1 && b<=n && d>=1 && d<=n && a[b][d]+x == 1 && flag[b][d])continue;
if(b>=1 && b<=n && d>=1 && d<=n && a[b][d]+x == 1 )dfs(b,d,l);
}
a[i][j]=x;
}
int main()
{
int m,k=1;
cin>>n>>m;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
scanf("%1d",&a[i][j]);
while(k<=m){
int in1,in2;
scanf("%d%d",&in1,&in2);
if(flag[in1][in2]!=0)cout<<ans[flag[in1][in2]];
else dfs(in1,in2,k),cout<<ans[flag[in1][in2]];
k++;
if(k<=m)cout<<endl;
}
return 0;
}
P1036 [NOIP2002 普及组] 选数
和全排列写法有些许类似,感觉比全排列问题简单
#include
#define eps 1e-15
using namespace std;
int n,k,a[25],sum,ans;
bool judge(int i){
bool f=true;
for(int j=2; j<=i/2; j++){
if(i%j==0){f=false;break;}
}
return f;
}
void dfs(int ne, int i){
if(i>k){
if(judge(sum))ans++;
return;
}
for(int j=ne; j<=n-k+i; j++)
sum+=a[j],dfs(j+1,i+1),sum-=a[j];
}
int main()
{
cin>>n>>k;
for(int i=1; i<=n; i++)cin>>a[i];
dfs(1,1);
cout<<ans;
return 0;
}
P2372 yyy2015c01挑战算周长
题意挺绕的,简单来说就是求连通块的周长。连通块内的每个点贡献的周长确定,应该看其上下左右又没有碰墙或不属于连通块的点。
#include
#define eps 1e-15
using namespace std;
int n,m,visit[25][25],nx[8]={-1,1,0,0,-1,-1,1,1},ny[8]={0,0,-1,1,-1,1,1,-1},ans;
char a[25][25];
void dfs(int x,int y){
if(visit[x][y] || a[x][y]=='.')return;
visit[x][y]=1;
for(int k=0; k<8; k++){
int xx=x+nx[k],yy=y+ny[k];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m)dfs(xx,yy);
if(k<=3 && (xx>n || xx<1 || yy<1 || yy>m || a[xx][yy]=='.'))ans++;
}
}
int main()
{
int x,y;
cin>>n>>m>>x>>y;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin>>a[i][j];
dfs(x,y);
cout<<ans;
return 0;
}
CF510B Fox And Two Dots
P1665 正方形计数
对于回溯算法,我现在也没有深入了解,只是在课上听了听,只敲了一个图的m着色问题。
回溯用深度优先搜索的方法搜索解空间,并通过剪枝去掉不可能产生解的子空间,回溯的空间诉求和深搜是一样的(废话。。。)。
#include
#define eps 1e-15
using namespace std;
int n,k,m,a[105][105],co[105],ans;
bool judge(int i){
for(int j=1; j<i; j++){
if(a[i][j]==1 && co[i]==co[j])return false;
}
return true;
}
void mapColor(int i){
if(i>n)ans++;
else {
for(int j=1; j<=m ;j++){
co[i]=j;
if(judge(i))mapColor(i+1);
}
}
}
int main()
{
cin>>n>>k>>m;
int r,c;
for(int i=1 ;i<=k; i++){
cin>>r>>c;
a[r][c]=1;
a[c][r]=1;
}
mapColor(1);
cout<<ans;
return 0;
}
广搜是一层一层的进行搜索,每找到一个符合条件的结点就入队,在下一次循环出队,直到队空或产生最终结果。
每一层的状态都是最优的,但需要大量空间,存储每个结点产生的状态。
P1443 马的遍历
应该算是广搜的模板题了。
#include
#define eps 1e-15
using namespace std;
int visit[405][405],nx[8]={-2,-1,1,2,2,1,-1,-2},ny[8]={1,2,2,1,-1,-2,-2,-1};
struct horse{
int xx,yy,p;
horse(int xx,int yy,int pp){make(xx,yy,pp);}
void make(int xxx,int yyy,int pp){xx=xxx;yy=yyy;p=pp;}
};
queue<horse> q;
int main()
{
int n,m,x,y,a[405][405],num=0;
cin>>n>>m>>x>>y;
memset(visit,0,sizeof(visit));
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
a[i][j]=-1;
visit[x][y]=1;
horse t(x,y,0);
q.push(t);
while(!q.empty()){
t=q.front();
q.pop();
x=t.xx,y=t.yy;num=t.p;
a[x][y]=t.p;
//visit[x][y]=1;可能造成同一个点多次入队,在数据量大时就会MLE
for(int i=0; i<8; i++){
int xx=x+nx[i],yy=y+ny[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m && !visit[xx][yy])t.make(xx,yy,num+1),q.push(t),visit[xx][yy]=1;
}
}
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++)
printf("%-5d",a[i][j]);
printf("\n");
}
return 0;
}
P1746 离开中山路
找到最终结果直接结束
#include
#define eps 1e-15
using namespace std;
int ans[1005][1005],a[1005][1005];
int main()
{
int n,x1,x2,y1,y2;
int nx[4]={-1,1,0,0},ny[4]={0,0,-1,1};
queue<pair<int,int>> q;
cin>>n;
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
scanf("%1d",&a[i][j]);
cin>>x1>>y1>>x2>>y2;
pair<int,int> t(x1,y1);
q.push(t);
a[x1][y1]=1;
while(!q.empty()){
t=q.front();
q.pop();
x1=t.first,y1=t.second;
for(int i=0; i<4; i++){
int xx=x1+nx[i],yy=y1+ny[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&a[xx][yy]==0)a[xx][yy]=1,ans[xx][yy]=ans[x1][y1]+1,t=make_pair(xx,yy),q.push(t);
}
if(ans[x2][y2]!=0)break;//找到结果一定最优,直接退出
}
cout<<ans[x2][y2];
return 0;
}
P1332 血色先锋队
如有错误或不足,还请您指出!