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的最小值
相关例题
马的遍历
这道题是一道典型的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
这一道题则是经典的使用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
这道题看到很多大佬用数学方式推,但是当时我第一时间想到的是使用二分,因为如果这两个数满足条件则一定会满足下列式子
: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");
}
}