Acwing算法提高课全程笔记(持续更新~)

※算法提高课

文章目录

  • ※算法提高课
    • 第一章、动态规划
      • 1.1 数字三角形模型
        • 1015.摘花生
        • 1018.最低通行费
        • 1027.方格取数
      • 1.2.1 最长上升子序列模型(一)
        • 1017.怪盗基德的滑翔翼
        • 1014.登山
        • 1012.友好城市
        • 1016.最大上升子序列和
      • 1.2.2 最长上升子序列模型(二)
        • 1010.拦截导弹(LIS+贪心)
        • 187.导弹防御系统(贪心+dfs)
        • 272.最长公共上升子序列
      • 1.3.1 背包模型
    • 第二章、搜索
      • 2.1.1.1 Flood Fill算法
        • 1097.池塘计数
        • 1098.城堡问题
        • 1106.山峰和山谷
      • 2.1.1.2 最短路模型
        • 1076.迷宫问题(求BFS完整路径)
        • 1100.抓住那头牛(一维线段上的BFS)
      • 2.1.2.1 BFS中的多源BFS
        • 173.矩阵距离
      • ~~2.1.2.2 最小步数模型~~(先复习基础课八数码)
      • 2.1.2.3 双端队列广搜
        • 175.电路维修
      • 2.1.3.1 双向广搜(一般用于最小步数模型)
        • 190.字串变换
      • 2.1.3.2 A*算法

第一章、动态规划

1.1 数字三角形模型

1015.摘花生

#include
using namespace std;
const int N=105;
int f[N][N];
int main(){
     
	int t;
	cin>>t;
	while(t--){
     
		int n,m;
		cin>>n>>m;
		for(int i=1;i<=n;i++){
     
			for(int j=1;j<=m;j++){
     
				cin>>f[i][j];
			}
		}
		for(int i=1;i<=n;i++){
     
			for(int j=1;j<=m;j++){
     
				f[i][j]=max(f[i-1][j],f[i][j-1])+f[i][j];
			}
		}
		cout<<f[n][m]<<endl;
	}
}

1018.最低通行费

首先题目说走不超过2n-1个格子,因为走大直角恰好为2n-1个格子,所以我们如果不走回头路恰好为2n-1个格子,这样可以完美转换成数字三角形模型。

其次要注意的点,该题要求的是最小值,和上一题不同的是,我们需要特殊处理第一行和第一列的数据

#include
using namespace std;
const int N=105;
int w[N][N],f[N][N];
int main(){
     
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
     
		for(int j=1;j<=n;j++){
     
			cin>>w[i][j];
		}
	}
	for(int i=1;i<=n;i++){
     
		f[1][i]=f[1][i-1]+w[1][i];
		f[i][1]=f[i-1][1]+w[i][1];
	}
	for(int i=2;i<=n;i++){
     
		for(int j=2;j<=n;j++){
     
			f[i][j]=min(f[i-1][j],f[i][j-1])+w[i][j];
		}
	}
	cout<<f[n][n];
}

1027.方格取数

题目描述:

设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:

Acwing算法提高课全程笔记(持续更新~)_第1张图片

某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点。

在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。

此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。

分析:

集合表示:

对于只走一次的问题, f [ i , j ] f[i,j] f[i,j]表示所有从 ( 1 , 1 ) (1,1) (1,1)走到 ( i , j ) (i,j) (i,j)的路径最大值

f [ i , j ] = m a x ( f [ i − 1 , j ] , f [ i , j − 1 ] + w [ i , j ] ) f[i,j]=max(f[i-1,j],f[i,j-1]+w[i,j]) f[i,j]=max(f[i1,j],f[i,j1]+w[i,j])

对本题来说,走两次,我们用一个四维状态表示

f [ i 1 , j 1 , i 2 , j 2 ] f[i_1,j_1,i_2,j_2] f[i1,j1,i2,j2]表示所有从 ( 1 , 1 ) (1,1) (1,1)分别走到 ( i 1 , j 1 ) (i_1,j_1) (i1,j1) ( i 2 , j 2 ) (i_2,j_2) (i2,j2)的路径的最大值

如何处理“同一个格子不能被同时选择”?

只有在 i 1 + j 1 = = i 2 + j 2 i_1+j_1==i_2+j_2 i1+j1==i2+j2时,两条路径的格子才可能重合(或者说,我们的状态表示中,限制 ( i 1 , j 1 ) (i_1,j_1) (i1,j1) ( i 2 , j 2 ) (i_2,j_2) (i2,j2)的步数一定是相同的,我们一定有上面的式子)

因此我们的状态表示可以优化到三维 f ( k , i 1 , i 2 ) f(k,i_1,i_2) f(k,i1,i2),表示所有从 ( 1 , 1 ) (1,1) (1,1)分别走到 ( i 1 , k − i 1 ) (i_1,k-i_1) (i1,ki1) ( i 2 , k − i 2 ) (i_2,k-i_2) (i2,ki2)的路径最大值

k k k表示两条路线当前走到的格子的横纵坐标之和

k = i 1 + j 1 = i 2 + j 2 k=i_1+j_1=i_2+j_2 k=i1+j1=i2+j2

集合划分:

0表示从上面转移过来,1表示从左边转移过来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
Acwing算法提高课全程笔记(持续更新~)_第2张图片

根据上图我们可以得出状态转移方程

(如果x1!=x2)

f [ k ] [ i 1 ] [ i 2 ] = m a x ( f [ k − 1 ] [ i 1 − 1 ] [ i 2 − 1 ] , f [ k − 1 ] [ i 1 ] [ i 2 − 1 ] , f [ k − 1 ] [ i 1 − 1 ] [ i 2 ] , f [ k − 1 ] [ i 1 ] [ i 2 ] ) + w [ i 1 ] [ j 1 ] + w [ i 2 ] [ j 2 ] f[k][i1][i2]=max(f[k-1][i1-1][i2-1],f[k-1][i1][i2-1],f[k-1][i1-1][i2],f[k-1][i1][i2])+w[i1][j1]+w[i2][j2] f[k][i1][i2]=max(f[k1][i11][i21],f[k1][i1][i21],f[k1][i11][i2],f[k1][i1][i2])+w[i1][j1]+w[i2][j2]

(如果x1==x2),去掉后面的 w [ i 2 ] [ j 2 ] w[i2][j2] w[i2][j2]即可

#include
using namespace std;
const int N=15;
int f[N+N][N][N],w[N][N];
int main(){
     
	int n;
	cin>>n;
	int a,b,c;
	while(cin>>a>>b>>c&&a||b||c) w[a][b]=c;
	for(int k=2;k<=n+n;k++){
     
		for(int i1=1;i1<=n;i1++){
     
			for(int i2=1;i2<=n;i2++){
     
				int j1=k-i1,j2=k-i2;
				if(j1>=1&&j1<=n&&j2>=1&&j2<=n){
     
					int &x=f[k][i1][i2];//引用类型,给f[k][i1][i2]起别名
					int t=w[i1][j1];
					if(i1!=i2) t+=w[i2][j2];
					x=max(x,f[k-1][i1-1][i2-1]+t);
					x=max(x,f[k-1][i1][i2-1]+t);
					x=max(x,f[k-1][i1-1][i2]+t);
					x=max(x,f[k-1][i1][i2]+t);
				}
			}
		}
	}
	cout<<f[n+n][n][n];
}

拓展习题:275.传纸条

1.2.1 最长上升子序列模型(一)

又称LIS问题

Acwing算法提高课全程笔记(持续更新~)_第3张图片

1017.怪盗基德的滑翔翼

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。

初始时,怪盗基德可以在任何一幢建筑的顶端。

他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。

因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。

他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。

请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

Acwing算法提高课全程笔记(持续更新~)_第4张图片

分析:正向和反向分别做一遍LIS

#include
using namespace std;
const int N=105;
int a[N],f[N];
int main(){
     
	int k;
	cin>>k;
	while(k--){
     
		int n;
		cin>>n;
		for(int i=1;i<=n;i++) cin>>a[i];
		int res=0;
		for(int i=1;i<=n;i++){
     
			f[i]=1;
			for(int j=1;j<i;j++){
     
				if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
			}
			res=max(res,f[i]);
		}
		for(int i=n;i>=1;i--){
     
			f[i]=1;
			for(int j=n;j>i;j--){
     
				if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
 			}
 			res=max(res,f[i]);
		}
		cout<<res<<endl;
	}
}

1014.登山

五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。

同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。

队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?

Acwing算法提高课全程笔记(持续更新~)_第5张图片

分析:

按照图中所述的三个条件,我们需要的路线形状一定是上图中所述,所以题目转换成所有形状是上面这种的子序列长度的最大值

先正向求一遍LIS,存在f数组中,再反向求一遍LIS,存在g数组中,最后res=max(res,f[i]+g[i]-1),减1的原因是因为f[i]+g[i]i点被计算了两次

#include
using namespace std;
const int N=1005;
int a[N],f[N],g[N];
int main(){
     
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
     
		f[i]=1;
		for(int j=1;j<i;j++){
     
			if(a[j]<a[i]) f[i]=max(f[i],f[j]+1);
		}
	}
	for(int i=n;i>=1;i--){
     
		g[i]=1;
		for(int j=n;j>i;j--){
     
			if(a[j]<a[i]) g[i]=max(g[i],g[j]+1);
		}
	}
	int res=0;
	for(int i=1;i<=n;i++){
     
		res=max(res,f[i]+g[i]-1);
	}
	cout<<res;
}

1012.友好城市

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。

北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。

编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

**思路:**将每一对桥看作自变量——因变量的组合,对这些组合按照自变量从小到大排序,从中选择组合若自变量从小到大选择,因变量只能选择上升的序列,否则就会有交叉,则该题可以转化为LIS问题

Acwing算法提高课全程笔记(持续更新~)_第6张图片

代码:

#include
using namespace std;
typedef pair<int,int> PII;
const int N=5005;
PII pi[N];
vector<int> b;
int f[N];
int main(){
     
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
     
		cin>>pi[i].first>>pi[i].second;
	}
	sort(pi,pi+n);
	for(int i=0;i<n;i++){
     
		b.push_back(pi[i].second);
	}
	int res=0;
	for(int i=0;i<n;i++){
     
		f[i]=1;
		for(int j=0;j<i;j++){
     
			if(b[j]<b[i]) f[i]=max(f[i],f[j]+1);
		}
		res=max(res,f[i]);
	}
	cout<<res;
}

1016.最大上升子序列和

#include
using namespace std;
const int N=1005;
int a[N],f[N];
int main(){
     
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	int res=0;
	for(int i=1;i<=n;i++){
     
		f[i]=a[i];
		for(int j=1;j<i;j++){
     
			if(a[j]<a[i]) f[i]=max(f[i],f[j]+a[i]);
		}
		res=max(res,f[i]);
	}
	cout<<res;
}

1.2.2 最长上升子序列模型(二)

1010.拦截导弹(LIS+贪心)

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

分析:

对于第一问,直接使用LIS的反向版本

对于第二问,贪心法解决:

Acwing算法提高课全程笔记(持续更新~)_第7张图片

#include
using namespace std;
const int N=1005;
int a[N],f[N],g[N];
int n;
int main(){
     
	while(cin>>a[n]) n++;
	int res=0;
	for(int i=0;i<n;i++){
     
		f[i]=1;
		for(int j=0;j<i;j++){
     
			if(a[j]>=a[i]) f[i]=max(f[i],f[j]+1);
		}
		res=max(res,f[i]);
	}
	cout<<res<<endl;
	int cnt=0;
	for(int i=0;i<n;i++){
     
		int k=lower_bound(g,g+cnt,a[i])-g;//二分查找g中第一个>=a[i]的数
		g[k]=a[i];
		if(k>=cnt) cnt++;
	}
	cout<<cnt;
}

187.导弹防御系统(贪心+dfs)

为了对抗附近恶意国家的威胁,R国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为3和高度为4的两发导弹,那么接下来该系统就只能拦截高度大于4的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

分析:

dfs+贪心,暴力搜索每一个数是添加到上升子序列中,还是添加到下降子序列中

#include
using namespace std;
const int N=55;
int q[N],up[N],down[N];
int n;
int ans;
void dfs(int u,int su,int sd){
     
	if(su+sd>=ans) return;
	if(u==n){
     
		ans=su+sd;
		return;
	}
	//情况1:将当前数放到上升子序列中
	int k=0;
	while(k<su && up[k]>=q[u]) k++;
	int t=up[k];
	up[k]=q[u];
	if(k<su) dfs(u+1,su,sd);
	else dfs(u+1,su+1,sd);
	up[k]=t;
	//情况2:将当前数放到下降子序列中
	k=0;
	while(k<sd && down[k]<=q[u]) k++;
	t=down[k];
	down[k]=q[u];
	if(k<sd) dfs(u+1,su,sd);
	else dfs(u+1,su,sd+1);
	down[k]=t; 
}
int main(){
     
	while(cin>>n,n){
     
		for(int i=0;i<n;i++) cin>>q[i];
		ans=n;
		dfs(0,0,0);
		cout<<ans<<endl;
	}
}

272.最长公共上升子序列

对于两个数列A和B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

分析:

状态表示 f [ i , j ] f[i,j] f[i,j]:集合:所有由第一个序列的前i个字母,和第二个序列的前j个字母构成的,且以b[j]结尾的公共上升子序列

​ 属性:Max

状态计算(对应集合划分):

首先依据公共子序列中是否包含a[i],将f[i][j]所代表的集合划分成两个不重不漏的子集:

  • 不包含a[i]的子集,最大值是f[i - 1][j]
  • 包含a[i]的子集,将这个子集继续划分,依据是子序列的倒数第二个元素在b[]中是哪个数:
    • 子序列只包含b[j]一个数,长度是1;
    • 子序列的倒数第二个数是b[1]的集合,最大长度是f[i - 1][1] + 1
    • 子序列的倒数第二个数是b[j - 1]的集合,最大长度是f[i - 1][j - 1] + 1

朴素做法:

#include
using namespace std;
const int N=3005;
int a[N],b[N];
int f[N][N];
int main(){
     
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	for(int i=1;i<=n;i++){
     
		for(int j=1;j<=n;j++){
     
			f[i][j]=f[i-1][j];//不包含a[i]的情况
			if(a[i]==b[j]){
     
				f[i][j]=max(f[i][j],1);//空集的情况,也就是只包含b[j]一个数 
				for(int k=1;k<j;k++){
     
					if(b[k]<b[j])
						f[i][j]=max(f[i][j],f[i-1][k]+1);
				} 
			} 
		}
	}
	int res=0;
	for(int i=1;i<=n;i++) res=max(res,f[n][i]);
	cout<<res;
}

优化版本:

#include
using namespace std;
const int N=3005;
int a[N],b[N];
int f[N][N];
int main(){
     
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	for(int i=1;i<=n;i++){
     
        int maxv=1;
		for(int j=1;j<=n;j++){
     
			f[i][j]=f[i-1][j];//不包含a[i]的情况
			if(a[i]==b[j]) f[i][j]=max(f[i][j],maxv);
            if(b[j]<a[i]) maxv=max(maxv,f[i][j]+1);
		}
	}
	int res=0;
	for(int i=1;i<=n;i++) res=max(res,f[n][i]);
	cout<<res;
}

1.3.1 背包模型

当空间优化成1维之后,只有完全背包的体积是从小到大循环的

for 物品

​		for 体积

​				for 决策(划分依据)

第二章、搜索

2.1.1.1 Flood Fill算法

可以在线性时间复杂度内,找到某个点所在的连通块

1097.池塘计数

农夫约翰有一片 N∗M 的矩形土地。

最近,由于降雨的原因,部分土地被水淹没了。

现在用一个字符矩阵来表示他的土地。

每个单元格内,如果包含雨水,则用”W”表示,如果不含雨水,则用”.”表示。

现在,约翰想知道他的土地中形成了多少片池塘。

每组相连的积水单元格集合可以看作是一片池塘。

每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。

请你输出共有多少片池塘,即矩阵中共有多少片相连的”W”块。

输入样例:

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出样例:

3

标准代码(熟记)

#include
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=1010,M=N*N;
int n,m;
char g[N][N];
PII q[M];
bool st[N][N];
void bfs(int sx,int sy){
     
	int hh=0,tt=0;
	q[0]={
     sx,sy};
	st[sx][sy]=1;
	while(hh<=tt){
     
		PII t=q[hh++];
		for(int i=t.x-1;i<=t.x+1;i++){
     
			for(int j=t.y-1;j<=t.y+1;j++){
     
				if(i==t.x && j==t.y) continue;
				if(i<0 || i>=n ||j<0|| j>=m) continue;
				if(g[i][j]=='.'||st[i][j]) continue;
				q[++tt]={
     i,j};
				st[i][j]=1;
			}
		}
	}
}
int main(){
     
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++) scanf("%s",g[i]);
	int cnt=0;
	for(int i=0;i<n;i++){
     
		for(int j=0;j<m;j++){
     
			if(g[i][j]=='W'&&!st[i][j]){
     
				bfs(i,j);
				cnt++;
			}
		}
	}
	printf("%d\n",cnt);
	return 0;
}

1098.城堡问题

   1   2   3   4   5   6   7  
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #
   #---#########---#####---#---#
 4 #   #   |   |   |   |   #   #
   #############################
           (图 1)

   #  = Wall   
   |  = No wall
   -  = No wall

   方向:上北下南左西右东。

图1是一个城堡的地形图。

请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。

城堡被分割成 m∗n个方格区域,每个方格区域可以有0~4面墙。

注意:墙体厚度忽略不计。

每个方块中墙的特征由数字 P 来描述,我们用1表示西墙,2表示北墙,4表示东墙,8表示南墙,P 为该方块包含墙的数字之和。

例如,如果一个方块的 P 为3,则 3 = 1 + 2,该方块包含西墙和北墙。

城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。

分析:

该题我们不需要把完整的地图画出来,判断一个格子能否转移到另一个格子,只需要判断g[x][y]>>i &1是不是1即可,是1说明有墙过不去。其他的完全和1097.池塘计数一样

Acwing算法提高课全程笔记(持续更新~)_第8张图片

#include
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=55;
int g[N][N],n,m;
bool st[N][N];
PII q[N*N];
int bfs(int sx,int sy){
     
	int dx[4]={
     0,-1,0,1},dy[4]={
     -1,0,1,0};
	int hh=0,tt=0;
	st[sx][sy]=1;
	q[0]={
     sx,sy};
	while(hh<=tt){
     
		PII t=q[hh++];
		for(int i=0;i<4;i++){
     
			int a=t.x+dx[i];
			int b=t.y+dy[i];
			if(a<0||a>=n||b<0||b>=m) continue;
			if(g[t.x][t.y]>>i &1 ) continue;
			if(st[a][b]) continue;
			q[++tt]={
     a,b};
			st[a][b]=1;
		}
	}
}
int main(){
     
	cin>>n>>m;
	for(int i=0;i<n;i++){
     
		for(int j=0;j<m;j++){
     
			cin>>g[i][j];
		}
	}
	int ans=0,area=0;
	for(int i=0;i<n;i++){
     
		for(int j=0;j<m;j++){
     
			if(!st[i][j]){
     
				ans++;
				area=max(area,bfs(i,j));
			}
		}
	}
	cout<<ans<<endl;
	cout<<area<<endl;
	return 0;
}

1106.山峰和山谷

你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。

分析:在原来bfs的基础上,加上统计连通块周围格子的大小关系

#include
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=1005;
int g[N][N],n;
bool st[N][N];
PII q[N*N];
void bfs(int sx,int sy,bool& has_low,bool& has_high){
     
	int hh=0,tt=0;
	q[0]={
     sx,sy};
	st[sx][sy]=1;
	while(hh<=tt){
     
		PII t=q[hh++];
		for(int i=t.x-1;i<=t.x+1;i++){
     
			for(int j=t.y-1;j<=t.y+1;j++){
     
				if(i==t.x && j==t.y) continue;
				if(i<0 || i>=n||j<0||j>=n) continue;
				if(g[i][j]!=g[t.x][t.y]){
     
					if(g[i][j]>g[t.x][t.y]) has_high=1;
					else has_low=1;
				}else if(!st[i][j]){
     
					q[++tt]={
     i,j};
					st[i][j]=1;
				}
			}
		}
	}
}
int main(){
     
	cin>>n;
	for(int i=0;i<n;i++){
     
		for(int j=0;j<n;j++){
     
			cin>>g[i][j];
		}
	}
	int peak=0,valley=0;
	for(int i=0;i<n;i++){
     
		for(int j=0;j<n;j++){
     
			bool has_higher=0,has_lower=0;
			if(!st[i][j]){
     
				bfs(i,j,has_lower,has_higher);
				if(!has_higher) peak++;
				if(!has_lower) valley++;
			}
		}
	}
	printf("%d %d",peak,valley);
}

2.1.1.2 最短路模型

当所有边权重相等时,从起点开始bfs,可以得到到所有点的最短路

1076.迷宫问题(求BFS完整路径)

要求输出bfs最短路上的完整路径

#include
#define x first
#define y second
using namespace std;
const int N=1005;
typedef pair<int,int> PII;
PII q[N*N],pre[N][N];
int g[N][N];
bool st[N][N];
int n;
void bfs(int sx,int sy){
     
	int dx[4]={
     -1,0,1,0},dy[4]={
     0,1,0,-1};
	int hh=0,tt=0;
	st[sx][sy]=1;
	q[0]={
     sx,sy};
	memset(pre,-1,sizeof pre);
	pre[sx][sy]={
     0,0};
	while(hh<=tt){
     
		PII t=q[hh++];
		for(int i=0;i<4;i++){
     
			int a=t.x+dx[i],b=t.y+dy[i];
			if(a<0||a>=n||b<0||b>=n) continue;
			if(st[a][b]) continue;
			if(g[a][b]==1) continue;
			pre[a][b]=t;
			st[a][b]=1;
			q[++tt]={
     a,b};
		}
	}
}
int main(){
     
	scanf("%d",&n);
	for(int i=0;i<n;i++){
     
		for(int j=0;j<n;j++){
     
			scanf("%d",&g[i][j]);
		}
	}
	bfs(n-1,n-1);
	PII end(0,0);
	while(1){
     
		printf("%d %d\n",end.x,end.y);
		if(end.x==n-1&&end.y==n-1) break;
		end=pre[end.x][end.y];
	}
}

1100.抓住那头牛(一维线段上的BFS)

农夫知道一头牛的位置,想要抓住它。

农夫和牛都位于数轴上,农夫起始位于点 N N N,牛位于点 K K K

农夫有两种移动方式:

  1. X X X 移动到 X − 1 X−1 X1 X + 1 X+1 X+1,每次移动花费一分钟
  2. X X X 移动到 2 ∗ X 2∗X 2X,每次移动花费一分钟

假设牛没有意识到农夫的行动,站在原地不动。

农夫最少要花多少时间才能抓住牛?

#include
using namespace std;
const int N=2e5+5;
int g[N],dist[N];
int q[N];
int n,k;
int bfs(){
     
	q[0]=n;
	memset(dist,-1,sizeof dist);
	int hh=0,tt=0;
	dist[n]=0;
	while(hh<=tt){
     
		int t=q[hh++];
		if(t==k) return dist[t];
		if(t+1<N && dist[t+1]==-1){
     
			dist[t+1]=dist[t]+1;
			q[++tt]=t+1;
		}
		if(t-1>=0 && dist[t-1]==-1){
     
			dist[t-1]=dist[t]+1;
			q[++tt]=t-1;
		}
		if(2*t<N &&dist[2*t]==-1){
     
			dist[2*t]=dist[t]+1;
			q[++tt]=2*t;
		}
	}
	return -1;
}
int main(){
     
	cin>>n>>k;
	cout<<bfs()<<endl;
	return 0;
}

2.1.2.1 BFS中的多源BFS

173.矩阵距离

给定一个N行M列的01矩阵A,A[i][j]A[k][l] 之间的曼哈顿距离定义为:

d i s t ( A [ i ] [ j ] , A [ k ] [ l ] ) = ∣ i − k ∣ + ∣ j − l ∣ dist(A[i][j],A[k][l])=|i−k|+|j−l| dist(A[i][j],A[k][l])=ik+jl

输出一个N行M列的整数矩阵B,其中:

B [ i ] [ j ] = m i n 1 ≤ x ≤ N , 1 ≤ y ≤ M , A [ x ] [ y ] = 1 d i s t ( A [ i ] [ j ] , A [ x ] [ y ] ) B[i][j]=min_{1≤x≤N,1≤y≤M,A[x][y]=1}dist(A[i][j],A[x][y]) B[i][j]=min1xN,1yM,A[x][y]=1dist(A[i][j],A[x][y])

分析:

在这里插入图片描述

题目大意是,求出所有的0,到距离最近的1的曼哈顿距离,并存入b矩阵。

不妨将上面的式子反过来,求所有的1到每个0的最短路,也就是多源BFS。

我们可以假设一个虚拟原点,到所有1的距离为0,将所有的1入队,跑一遍BFS,最后的dist数组就是答案

#include
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=1005,M=N*N;
char g[N][N];
int dist[N][N];
PII q[M];
int n,m;
void bfs(){
     
	int dx[4]={
     -1,0,1,0},dy[4]={
     0,1,0,-1};
	memset(dist,-1,sizeof dist);
	int hh=0,tt=-1;
	for(int i=0;i<n;i++){
     
		for(int j=0;j<m;j++){
     
			if(g[i][j]=='1'){
     //将所有的数字1入队
				q[++tt]={
     i,j};
				dist[i][j]=0;
			}
		}
	}
	while(hh<=tt){
     
		PII t=q[hh++];
		for(int i=0;i<4;i++){
     
			int a=t.x+dx[i];
			int b=t.y+dy[i];
			if(a<0||a>=n||b<0||b>=m) continue;
			if(dist[a][b]!=-1) continue;
			dist[a][b]=dist[t.x][t.y]+1;
			q[++tt]={
     a,b};
		}
	}
}
int main(){
     
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++){
     
		cin>>g[i];
	}
	bfs();
	for(int i=0;i<n;i++){
     
		for(int j=0;j<m;j++){
     
			printf("%d ",dist[i][j]);
		}
		puts("");
	}
}

2.1.2.2 最小步数模型(先复习基础课八数码)

2.1.2.3 双端队列广搜

175.电路维修

**题目大意:**给一个地图,边权的值有0,有1,问从左上角走到右下角的最短距离

分析:

边权值不统一,导致普通的bfs算法不可使用,此时转换为一种类dijkstra的算法,将边权为0的边加入队头,将边权为1的边加入队尾,也就是所谓的双端队列广搜

#include
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=505;
char g[N][N];
bool st[N][N];
int n,m,dist[N][N];
void bfs(){
     
	memset(dist,0x3f,sizeof dist);//dijkstra算法中距离初始化为无穷
	memset(st,0,sizeof st);
	deque<PII> q;
	int dx[4]={
     -1,-1,1,1},dy[4]={
     -1,1,1,-1};
	int ix[4]={
     -1,-1,0,0},iy[4]={
     -1,0,0,-1};
	char str[5]="\\/\\/";//能正确抵达对角的线
	
	q.push_back({
     0,0});
	dist[0][0]=0;
	while(!q.empty()){
     
		PII t=q.front();
		q.pop_front();
		int x=t.x,y=t.y;
		if(st[x][y]) continue;//需要有一个标志数组,判断是不是被更新过(记住!!)
		st[x][y]=1;
		
		for(int i=0;i<4;i++){
     
			int a=x+dx[i],b=y+dy[i];
			if(a<0||a>n||b<0||b>m) continue;
			int ca=x+ix[i],cb=y+iy[i];
			int d=dist[t.x][t.y]+(g[ca][cb]!=str[i]);
			if(dist[a][b]>d){
     
				dist[a][b]=d;
				if(g[ca][cb]!=str[i]) q.push_back({
     a,b});
				else q.push_front({
     a,b});
			}
		}
	}
}
int main(){
     
	int t;
	scanf("%d",&t);
	while(t--){
     
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;i++) scanf("%s",g[i]);
		bfs();
		if(dist[n][m]==0x3f3f3f3f) puts("NO SOLUTION");
		else printf("%d\n",dist[n][m]);
	}
}

2.1.3.1 双向广搜(一般用于最小步数模型)

双向广搜涉及到的问题:

  • 双向广搜一般用在最小步数模型中,因为最小步数一般都是指数级别的,而最短路中一般不会太大,根据题意即可。
  • while 判断条件是两个队列必须都不为空,若为空还未搜到,则说明未联通。
  • substr的用法与熟练度。
  • 决策的时候从字符串的每一个位置进行决策,需要二维度空间,每个字符开始的位置进行判断是否满足。
  • 两种搜索方式。
    1)传统方式,交替搜索,有可能会出现一边搜索空间非常大的可能不采用。
    2)优化方式,通过判断两边哪个队列空间,让空间少的一边进行搜索,可保证两边搜索的时候时空比较平均,一般用这种方式比较好。

190.字串变换

已知有两个字串 A A A, B B B 及一组字串变换的规则(至多6个规则):

A 1 A_1 A1 -> B 1 B_1 B1

A 2 A_2 A2 -> B 2 B_2 B2

规则的含义为:在 A A A 中的子串 A 1 A_1 A1 可以变换为 B 1 B_1 B1、$A_2 $可以变换为 B 2 B_2 B2…。

例如: A A A=’abcd’ B B B=’xyz’

变换规则为:

‘abc’->‘xu’ ‘ud’->‘y’ ‘y’->‘yz’

则此时, A A A 可以经过一系列的变换变为 B B B,其变换的过程为:

‘abcd’->‘xud’->‘xy’->‘xyz’

共进行了三次变换,使得 A A A 变换为 B B B

分析:

(BFS,双向BFS) O ( ( L N ) 5 ) O((LN)^5) O((LN)5)
假设每次决策数量是 K K K,那么如果直接BFS,最坏情况下的搜索空间是 K 10 K^{10} K10,非常大,所以会TLE或者MLE。

如果采用双向BFS,则可以把搜索空间降到 2 K 5 2K^5 2K5。在实际测试中只需 20ms 左右,剪枝效果很好。

BFS的扩展方式是:分别枚举在原字符串中使用替换规则的起点,和所使用的的替换规则。

#include
using namespace std;
const int N=6;
string a[N],b[N];
int n;
int extend(queue<string>& q,unordered_map<string,int>& da,unordered_map<string,int>& db,string a[],string b[]){
     
	string t=q.front();
	q.pop();
	for(int i=0;i<t.size();i++){
     //枚举串a的起点
		for(int j=0;j<n;j++){
     //枚举替换方案
			if(t.substr(i,a[j].size())==a[j]){
     //如果串a截取的这段,和替换方案相同
				string state=t.substr(0,i)+b[j]+t.substr(i+a[j].size());//新串
				if(db.count(state)) return da[t]+1+db[state];//如果在反向状态中存在,说明重合了,输出答案
				if(da.count(state)) continue;//在正向搜索中存在,则舍弃
				da[state]=da[t]+1;//更新距离
				q.push(state);//插入队列
			}
		}
	}
	return 11;
}
int bfs(string A,string B){
     
	queue<string> qa,qb;
	unordered_map<string,int> da,db;//记录距离
	qa.push(A),da[A]=0;
	qb.push(B),db[B]=0;
	
	while(qa.size() && qb.size()){
     
		int t;
		if(qa.size()<=qb.size()) t=extend(qa,da,db,a,b);//每次拓展队列中状态数少的,保持平衡
		else t=extend(qb,db,da,b,a);
		
		if(t<=10) return t;
	}
	return 11;//返回一个大于10的数,表示答案不存在
}
int main(){
     
	string A,B;
	cin>>A>>B;
	while(cin>>a[n]>>b[n]) n++;
	
	int step=bfs(A,B);
	if(step>10) puts("NO ANSWER!");
	else printf("%d\n",step);
	
	return 0;
}

2.1.3.2 A*算法

你可能感兴趣的:(笔记,ACM/ICPC/蓝桥杯,算法)