1.迷宫矩阵问题的变形应用
一般迷宫矩阵问题的解决方法比较固定,也不算抽象,我们只要放到坐标系里面通过坐标的移动,模拟在地图上行走,先拿之前的模板看一下:
char g[N][N];
struct PII{
int X,Y;
};
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1},st[N][N],n,m;
void bfs(int x,int y)
{
queue<PII>q;
q.push({x,y});
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int xx=t.X+dx[i],yy=t.Y+dy[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!st[xx][yy]&&g[xx][yy]!='#')
{
st[xx][yy]=1;
q.push({xx,yy});
}
}
}
}
而我们做题时可能会遇见好多不是像这一样的矩阵,但却是一样的解题思路,
例题:
题目描述
Pots
Time Limit: 1000MS | Memory Limit: 65536K | |||
Total Submissions: 38666 | Accepted: 15520 | Special Judge |
Description
You are given two pots, having the volume of A and B liters respectively. The following operations can be performed:
- FILL(i) fill the pot i (1 ≤ i ≤ 2) from the tap;
- DROP(i) empty the pot i to the drain;
- POUR(i,j) pour from pot i to pot j; after this operation either the pot j is full (and there may be some water left in the pot i), or the pot i is empty (and all its contents have been moved to the pot j).
Write a program to find the shortest possible sequence of these operations that will yield exactly C liters of water in one of the pots.
Input
On the first and only line are the numbers A, B, and C. These are all integers in the range from 1 to 100 and C≤max(A,B).
Output
The first line of the output must contain the length of the sequence of operations K. The following K lines must each describe one operation. If there are several sequences of minimal length, output any one of them. If the desired result can’t be achieved, the first and only line of the file must contain the word ‘impossible’.
Sample Input
3 5 4
Sample Output
6 FILL(2) POUR(2,1) DROP(1) POUR(2,1) FILL(2) POUR(2,1)
Source
Northeastern Europe 2002, Western Subregion
分析:
这个数据不多我们可以考虑用搜索来解决,但这个和和之前做的搜索好像没什么关系,但还是可以看出来联系的,这个要求的是要最小操作次数,这不就和bfs的性质正好一致了,就像早最短路径一样,现在主要的问题是如何用bfs实现这个。
我们不妨把思维打开,解决矩阵问题时,我们是用位移数组来实现坐标的移动,从而达到在地图上模拟的效果,这个题没有地图,但他每一次都有两个量,就是A瓶和B瓶的当前水量,我们可以把他看作是矩阵问题中的坐标x和y,对应的我们会发现位移数组还没替换,其实这个也比较容易,就是自己根据题意写一个函数来代替,如:
位移数组的变形:
PII solve(int x,PII ss)
{
// printf("x=%d a=%d b=%d\n",x,ss.a,ss.b);
if(x==1)ss.a=A;//A加满水
else if(x==2)ss.b=B;//B加满水
else if(x==3)ss.a=0;//A清空
else if(x==4)ss.b=0;//B清空
else if(x==5)//A-->B
{
int aa=ss.a-(B-ss.b);
if(aa<0)aa=0;
int bb=ss.b+ss.a;
if(bb>B)bb=B;
ss.a=aa;
ss.b=bb;
}
else //B-->A
{
int bb=ss.b-(A-ss.a);
if(bb<0)bb=0;
int aa=ss.a+ss.b;
if(aa>A)aa=A;
ss.a=aa;
ss.b=bb;
}
// printf("x=%d a=%d b=%d*\n",x,ss.a,ss.b);
return ss;
}
根据题意我们可以知道一共有6种操作,所以写出对应的解决方案就行,就和之前的位移数组一样原理,只不过那个比较简单。
完整代码:
#include<iostream>
#include<stdio.h>
#include<queue>
#include<vector>
using namespace std;
const int N=1e6+9;
int C,A,B,pre[N],k=0,flag,idx=1;
struct PII{
int a;
int b;
int X;//存操作类型的序号
int IDX;//可以看成是链表,方便打印答案
int pre;
}a[N],d[N];
bool st[199][199];
void print_ans(int x)
{
if(x==1)printf("FILL(1)\n");
else if(x==2)printf("FILL(2)\n");
else if(x==3)printf("DROP(1)\n");
else if(x==4)printf("DROP(2)\n");
else if(x==5)printf("POUR(1,2)\n");
else printf("POUR(2,1)\n");
}
PII solve(int x,PII ss)
{
// printf("x=%d a=%d b=%d\n",x,ss.a,ss.b);
if(x==1)ss.a=A;//A加满水
else if(x==2)ss.b=B;//B加满水
else if(x==3)ss.a=0;//A清空
else if(x==4)ss.b=0;//B清空
else if(x==5)//A-->B
{
int aa=ss.a-(B-ss.b);
if(aa<0)aa=0;
int bb=ss.b+ss.a;
if(bb>B)bb=B;
ss.a=aa;
ss.b=bb;
}
else //B-->A
{
int bb=ss.b-(A-ss.a);
if(bb<0)bb=0;
int aa=ss.a+ss.b;
if(aa>A)aa=A;
ss.a=aa;
ss.b=bb;
}
// printf("x=%d a=%d b=%d*\n",x,ss.a,ss.b);
return ss;
}
void bfs()
{
queue<PII>q;
st[0][0]=1;
q.push({0,0,0,0,-1});
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=1;i<=6;i++)
{
PII temp=t;
temp=solve(i,temp);
// printf("a=%d b=%d idx=%d pre=%d x=%d i=%d\n",temp.a,temp.b,temp.IDX,temp.pre,temp.X,i);
if(!st[temp.a][temp.b])
{
st[temp.a][temp.b]=1;
a[idx]=temp;
a[idx].X=i;//存当前操作序号
a[idx].pre=t.IDX;//存上一个操作的下标
a[idx].IDX=idx;//存自己的下标
// printf("a=%d b=%d idx=%d pre=%d x=%d\n",a[idx].a,a[idx].b,a[idx].IDX,a[idx].pre,a[idx].X);
if(a[idx].a==C||a[idx].b==C)
{
flag=1;
vector<int>s;
int cnt=0;
PII t=a[idx];
while(1)
{
cnt++;
s.push_back(t.X);
if(t.pre==0)break;
t=a[t.pre];
}
printf("%d\n",cnt);
for(int i=s.size()-1;i>=0;i--)
{
print_ans(s[i]);
}
return;
}
q.push(a[idx]);
idx++;//更新下标
}
}
}
}
int main()
{
cin>>A>>B>>C;
bfs();
if(flag==0)printf("impossible\n");
return 0;
}
这个题只是看起来有点难,但就是考察搜索的基本功,按照题意写就行了。
2.记忆化搜索
在做搜索题时,有时候有些题一直超时,而且感觉代码写的没问题,这时候就要思考一下如何优化算法来提高效率了,如何避免重复搜索导致的资源浪费?看具体的题目来分析一下
例题:
01迷宫
洛谷P1141
标签
题目描述
有一个仅由数字 0 与 1 组成的 n×n 格迷宫。若你位于一格 0 上,那么你可以移动到相邻 4 格中的某一格 1 上,同样若你位于一格 1 上,那么你可以移动到相邻 4 格中的某一格 0 上。
你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。
输入格式
第一行为两个正整数 n,m。
下面 n 行,每行 n 个字符,字符只可能是 0 或者 1,字符之间没有空格。
接下来 m 行,每行两个用空格分隔的正整数 i,j,对应了迷宫中第 i 行第 j 列的一个格子,询问从这一格开始能移动到多少格。
输出格式
m 行,对于每个询问输出相应答案。
输入输出样例
输入 #1复制
2 2 01 10 1 1 2 2
输出
4 4
说明/提示
对于样例,所有格子互相可达。
- 对于 100%100% 的数据,1≤n≤1000,1≤m≤100000。
分析:
看到这个题,我们下意识的就直接敲搜索的模板,但提交就会发现超时了,因为m非常大,如果每一次询问都搜索一遍的话肯定超时,这时候就可以想想如何优化一下算
先写一下一般思路的代码:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int n,m;
struct PII{
int X;
int Y;
};
const int N=1009;
int d[N][N],sum=0,ans,flag=0;
char g[N][N];
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
bool check(int x,int y,int xx,int yy)
{
if(g[x][y]==g[xx][yy])return 0;
else return 1;
}
void bfs(int x,int y)
{
sum++;
d[x][y]=1;
queue<PII>q;
q.push({x,y});
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int xx=t.X+dx[i],yy=t.Y+dy[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&!d[xx][yy]&&check(t.X,t.Y,xx,yy))
{
sum++;
//printf("xx=%d yy=%d sum=%d\n",xx,yy,sum);
flag=1;
d[xx][yy]=1;
q.push({xx,yy});
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>g[i][j];
}
}
while(m--)
{
int X,Y;
scanf("%d%d",&X,&Y);
bfs(X,Y);
if(ans==1)printf("0\n");
else printf("%d\n",sum);
sum=0;
memset(d,0,sizeof(d));
}
return 0;
}
这样的话会有好多重复搜索,通过观察可以知道可以把地图分成一块块的,因为相通的格子能走的最大格子数是相同的,所以每一块只用走一遍,故我们还要开一个地图和数组来存对应点能走的最大格子数(直接在原地图上操作也行)。
正确代码:
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int n,m;
struct PII{
int X;
int Y;
};
const int N=1009;
int d[N][N],sum=0,ans,cnt=1,a[10000009];//数组a注意不要开太小
char g[N][N];
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
bool check(int x,int y,int xx,int yy)
{
if(g[x][y]==g[xx][yy])return 0;
else return 1;
}
void bfs(int x,int y)
{
sum++;
d[x][y]=cnt;
queue<PII>q;
q.push({x,y});
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=0;i<4;i++)
{
int xx=t.X+dx[i],yy=t.Y+dy[i];
if(xx>=1&&xx<=n&&yy>=1&&yy<=n&&!d[xx][yy]&&check(t.X,t.Y,xx,yy))
{
sum++;
//printf("xx=%d yy=%d sum=%d\n",xx,yy,sum);
d[xx][yy]=cnt;//存一下对应数组a的下标
q.push({xx,yy});
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>g[i][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!d[i][j])
{
//printf("i=%d j=%d\n",i,j);
bfs(i,j);
a[cnt++]=sum;//存这一块地图的最大格子数,顺便更新下标,,方便下一次使用
sum=0;//初始化
}
}
}
while(m--)
{
int X,Y;
scanf("%d%d",&X,&Y);
printf("%d\n",a[d[X][Y]]);
}
return 0;
}
小结:
算法之路慢慢,吾将上下而求索,算法实在是太妙了,享受将一个个实际问题用代码来求解的乐趣,小趴菜努力学习ing~~~~