#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;
}
}
首先题目说走不超过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];
}
题目描述:
设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 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[i−1,j],f[i,j−1]+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,k−i1)和 ( i 2 , k − i 2 ) (i_2,k-i_2) (i2,k−i2)的路径最大值
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表示从左边转移过来
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
根据上图我们可以得出状态转移方程
(如果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[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]
(如果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.传纸条
又称LIS问题
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
分析:正向和反向分别做一遍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;
}
}
五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
分析:
按照图中所述的三个条件,我们需要的路线形状一定是上图中所述,所以题目转换成所有形状是上面这种的子序列长度的最大值
先正向求一遍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;
}
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。
北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。
编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
**思路:**将每一对桥看作自变量——因变量的组合,对这些组合按照自变量从小到大排序,从中选择组合若自变量从小到大选择,因变量只能选择上升的序列,否则就会有交叉,则该题可以转化为LIS问题
代码:
#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;
}
#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;
}
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
分析:
对于第一问,直接使用LIS的反向版本
对于第二问,贪心法解决:
#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;
}
为了对抗附近恶意国家的威胁,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;
}
}
对于两个数列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维之后,只有完全背包的体积是从小到大循环的
for 物品
for 体积
for 决策(划分依据)
可以在线性时间复杂度内,找到某个点所在的连通块
农夫约翰有一片 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;
}
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.池塘计数
一样
#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;
}
你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。
分析:在原来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);
}
当所有边权重相等时,从起点开始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];
}
}
农夫知道一头牛的位置,想要抓住它。
农夫和牛都位于数轴上,农夫起始位于点 N N N,牛位于点 K K K。
农夫有两种移动方式:
假设牛没有意识到农夫的行动,站在原地不动。
农夫最少要花多少时间才能抓住牛?
#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;
}
给定一个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])=∣i−k∣+∣j−l∣
输出一个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]=min1≤x≤N,1≤y≤M,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("");
}
}
**题目大意:**给一个地图,边权的值有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]);
}
}
双向广搜涉及到的问题:
- 双向广搜一般用在最小步数模型中,因为最小步数一般都是指数级别的,而最短路中一般不会太大,根据题意即可。
- while 判断条件是两个队列必须都不为空,若为空还未搜到,则说明未联通。
- substr的用法与熟练度。
- 决策的时候从字符串的每一个位置进行决策,需要二维度空间,每个字符开始的位置进行判断是否满足。
- 两种搜索方式。
1)传统方式,交替搜索,有可能会出现一边搜索空间非常大的可能不采用。
2)优化方式,通过判断两边哪个队列空间,让空间少的一边进行搜索,可保证两边搜索的时候时空比较平均,一般用这种方式比较好。
已知有两个字串 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;
}