DFS/BFS/二分专题+例题(马的遍历、Sticks、Obtain Two Zeroes)

DFS/BFS/二分

BFS

用数据结构中的队列(queue)来实现:把第一个状态放入队列中,随后将这一状态取出,搜索这个状态所能到达的状态,并把这些状态再放入队列中,以此类推。

代码基本格式:

#include<bits/stdc++.h>
using namespace std;

int a[450][450] = {0},n,m;
int v[450][450] = {0};
int dx[k] = {};
int dy[k] = {};//这两个数储存可走到的方向

struct node{
	int x,y;
}q,p;

queue<struct node> s;

int bfs(int x,int y){
	int xx,yy,i;
	a[x][y] = 0;
	q.x = x;q.y = y;
	s.push(q);
	while(s.size()){
		q = s.front();s.pop();
		for(i = 0;i<=k;i++){
			xx = q.x+dx[i];
			yy = q.y+dy[i];
			if(xx<1||yy<1||xx>n||yy>m){//越界则跳过
				continue;
			}
			if(v[xx][yy]!=0){//若已经被访问过则跳过
				continue;
			}
			p.x = xx;p.y = yy;
			a[p.x][p.y] = a[q.x][q.y] + 1;
			s.push(p);
		}
	}
}

DFS

DFS与BFS算是关联比较大,有许多题是dfs和bfs都可以做出来,这两个算法最大的别则是,dfs是一条路走到底,若走不通则返回上一层走另外一条路,找到答案,而bfs则是同一层的先遍历完,再遍历下一层。

代码基本格式

int dfs(状态){//dfs当前状态
	int i,j = 0;
	if(终止条件) return 1;
	//剪枝代码.....
	for(i = 0;i<n;i++){
		
		if(vis[i]) continue;//访问过则跳过
		
		if(符合条件){
			vis[i] = 1;//标记为以访问
			j = dfs(新的状态);//dfs新的状态
			if(j == 1) return 1;
			vis[i] = 0;//回溯时取消访问标记
		}
		//剪枝代码....
		
	}
	return 0;
}

二分

二分是一个比较实用的算法,有些题目当中二分的使用可以大大降低时间复杂度,具体思路便是每一步将划分两个区块,做判断筛掉一个半区。有时候在进行二分时先要将数组进行排序

代码基本格式

int l = 下界-1,r = 上界+1;
while(l+1<r){
    int mid = (l+r)>>1;
    if(check(mid))l = mid;
    else r = mid;
}
//l为满足check的最大值,r为不满足check的最小值

相关例题

马的遍历

1.马的遍历

这道题是一道典型的dfs和bfs都可以使用的例题,但是当看到最少走几步时,我们便可以知道使用bfs是更合理的。所以我们边用dfs来解决这道题

ac代码:

#include<bits/stdc++.h>
using namespace std;

int a[450][450] = {0},n,m;
int dx[] = {-2,-1,-2,-1,1,2,1,2};
int dy[] = {-1,-2,1,2,-2,-1,2,1};//dx,dy数组储存马下一步走的方向

struct node{//用结构体来表示点坐标
	int x,y;
}q,p;

queue<struct node> s;

int bfs(int x,int y){
	int xx,yy,i;
	a[x][y] = 0;
	q.x = x;q.y = y;
	s.push(q);
	while(s.size()){
		q = s.front();s.pop();
		for(i = 0;i<=7;i++){
			xx = q.x+dx[i];
			yy = q.y+dy[i];
			if(xx<1||yy<1||xx>n||yy>m){
				continue;
			}
			if(a[xx][yy]!=-1){
				continue;
			}
			p.x = xx;p.y = yy;
			a[p.x][p.y] = a[q.x][q.y] + 1;
			s.push(p);
		}
	}
}

int main(){
	int x,y,i,j;
	scanf("%d %d %d %d",&n,&m,&x,&y);
	for(i = 1;i<=n;i++){//初始化所有坐标为-1表示未走过
		for(j = 1;j<=m;j++){
			a[i][j] = -1;
		}
	}
	bfs(x,y);
	for(i = 1;i<=n;i++){
		for(j = 1;j<=m;j++){
			printf("%-5d",a[i][j]);
		}
		printf("\n");
	}
}

Sticks

2.Sticks

这一道题则是经典的使用dfs更好做的题,因为使用dfs可以更好表示当前的状态(用形参表示)。但是单用dfs是不行的,还需要对dfs进行剪枝否则会超时。并且我们知道如果先放大的,可以简化步骤,因为如果用小的可能要好多个才能凑齐一整枝。

ac代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n,sum = 0;
int a[70] = {0};//存放枝条数量
bool vis[70] = {0};//是否使用过

bool cmp(int a,int b){
	return a>b;
}

int dfsv(int spl,int con,int nowlen){//spl代表需要原来的长度,con代表已经用了多少枝条,弄完了呢代表当前所凑到的枝条长度
	int i,j = 0;
	if(nowlen == spl) {
		nowlen = 0;
	}
	if(con == n&&nowlen == 0) return 1;
	
	for(i = 0;i<n;i++){
		
		if(vis[i]) continue;
		
		if(a[i]+nowlen<=spl){
			vis[i] = 1;
			j = dfsv(spl,con+1,a[i]+nowlen);
			if(j == 1) return 1;
			vis[i] = 0;
		}
		
		if(a[i] == (spl - nowlen)||nowlen == 0) break;//a[i] == (spl - nowlen)则证明有这条枝条放不下,那边证明这个枝条之后也没有用,所以直接break,进行下一次spl的统计,nowlen == 0则证明没有用到枝条,也证明spl不正确,不需要继续计算。
		while(a[i] == a[i+1]) i++;//如果a[i]不行则与a[i]相同的枝条肯定也不行
		//这两处为剪枝
	}
	return 0;
}

int main(){
	int i,j,k;
	while(cin>>n&&n){
		sum = 0;
		for(i = 0;i < n;i++){
			scanf("%d",&a[i]);
			sum+=a[i];
		}
		sort(a,a+n,cmp);
		
		for(i = a[0];i<=sum;i++){
			memset(vis,0,sizeof(vis));
			if(sum%i == 0){
				k = dfsv(i,0,0);
				if(k) break;
			}
		}
		printf("%d\n",i);
	}
	return 0;
}

Obtain Two Zeroes

3.Obtain Two Zeroes

这道题看到很多大佬用数学方式推,但是当时我第一时间想到的是使用二分,因为如果这两个数满足条件则一定会满足下列式子

:a = 2x+y,b = x+2y,所以我们只需要找到一个x使得y也成立,便证明这两个数可行。

#include<cstdio>
#include<iostream>
using namespace std;

int chk(int x,int y){
	if(x == y&&y == 0) return 0;
	if(y == 0) return -1;
	if(x == 0) return 1;
	if( (x/y == 2)&&(x%y == 0) ) return 0;
	if( x < 2*y) return -1;
	if( x > 2*y) return 1;
}

int main(){
	int a,b,x,l,r,t,mid,z = 0;
	scanf("%d",&t);
	while(t--){
		z = 0;
		scanf("%d %d",&a,&b);
		if(a>b) swap(a,b);//使得b是两个数中最大的数,方便计算
		if(a == 0&&b == 0){
			printf("YES\n");
			continue;
		}
		if(2*a<b||a == 0||b == 0){//我们知道如果a<2b,或者a=0或者b=0,那么不管怎么样都凑不齐。
			printf("NO\n");
			continue;
		}
		l = 1;r = (b/2)+1;
		while(l<=r){
			mid = (l+r)/2;
			x = chk(a-mid,b-2*mid);
			if(x == 0){
				z = 1;
				break;
			}
			if(x == -1){
				l = mid + 1; 
			}
			if(x == 1){
				r = mid - 1;
			}
		}
		if(z) printf("YES\n");
		else printf("NO\n");
		
	}
}

猜你喜欢

转载自blog.csdn.net/m0_51687577/article/details/113826056