大家最近有在刷题不呀,不要忘记了我们之前一起定制的专属于自己的目标啊,如果忘记了的话,就赶紧通过传送门去回忆回忆
写在前面:
由于我算法也并不强,以前总是对刷算法报有一种以难为难的心态,大多数同学我想应该都是和我同一种情况,但是通过这几天的练习,我觉得我们只要肯练,我坚信我们是会有收获的,至少我觉得我已经可以克服那种一看题就感觉自己不会而不去动脑思考的思维了,至少我已经得到了大家的鼓励,我想你或许还在有为难情绪,但是你可以看着,和我一起,我们一起克服!
我们也会给大家分享我刷题的感想以及经验,如果你还在犹豫,那你可以先看着我们行动,看我们到底是否有成效,我们一路相互鼓励,我坚信我们是可以的!
概览:
解题思路
过50%代码
过80%代码
过100%代码
好了,话不多说,今天我们一起来练习的题目是:
2015-12-3:画图
大家可以点击链接直接去官网,具体题目如下:
用 ASCII 字符来画图是一件有趣的事情,并形成了一门被称为 ASCII Art 的艺术。
例如,下图是用 ASCII 字符画出来的 CSPRO 字样。
..____.____..____..____...___..
./.___/.___||.._.\|.._.\./._.\.
|.|...\___.\|.|_).|.|_).|.|.|.|
|.|___.___).|..__/|.._.<|.|_|.|
.\____|____/|_|...|_|.\_\\___/.
本题要求编程实现一个用 ASCII 字符来画图的程序,支持以下两种操作:
画线:给出两个端点的坐标,画一条连接这两个端点的线段。简便起见题目保证要画的每条线段都是水平或者竖直的。水平线段用字符 - 来画,竖直线段用字符 | 来画。如果一条水平线段和一条竖直线段在某个位置相交,则相交位置用字符 + 代替。
填充:给出填充的起始位置坐标和需要填充的字符,从起始位置开始,用该字符填充相邻位置,直到遇到画布边缘或已经画好的线段。注意这里的相邻位置只需要考虑上下左右 4个方向,如下图所示,字符 @ 只和 4 个字符 * 相邻。
.*.
*@*
.*.
输入格式
第 1行有三个整数 m,n 和 q。m 和 n 分别表示画布的宽度和高度,以字符为单位。q表示画图操作的个数。
第 2行至第 q+1行,每行是以下两种形式之一:
0 x1 y1 x2 y2:表示画线段的操作,(x1,y1)和 (x2,y2) 分别是线段的两端,满足要么 x1=x2 且 y1≠y2,要么 y1=y2 且 x1≠x2。
1 x y c:表示填充操作,(x,y)是起始位置,保证不会落在任何已有的线段上;c为填充字符,是大小写字母。
画布的左下角是坐标为 (0,0)的位置,向右为 x 坐标增大的方向,向上为 y坐标增大的方向。
这 q个操作按照数据给出的顺序依次执行。画布最初时所有位置都是字符 .(小数点)。
输出格式
输出有 n行,每行 m 个字符,表示依次执行这 q个操作后得到的画图结果。
数据范围2≤m,n≤100,0≤q≤100,
0≤x<m(x 表示输入数据中所有位置的 x 坐标),
0≤y<n(y 表示输入数据中所有位置的 y坐标)。
输入样例1:
4 2 3
1 0 0 B
0 1 0 2 0
1 0 0 A
输出样例1:
AAAA
A--A
输入样例2:
16 13 9
0 3 1 12 1
0 12 1 12 3
0 12 3 6 3
0 6 3 6 9
0 6 9 12 9
0 12 9 12 11
0 12 11 3 11
0 3 11 3 1
1 4 2 C
输出样例2:
................
...+--------+...
...|CCCCCCCC|...
...|CC+-----+...
...|CC|.........
...|CC|.........
...|CC|.........
...|CC|.........
...|CC|.........
...|CC+-----+...
...|CCCCCCCC|...
...+--------+...
................
本题思路:
直接按照题目要求模拟即可。
但是需要注意几点:
- 坐标问题: 画布的左下角是坐标为 (0,0)的位置,也就是我们数学中常用的数学坐标系,我们需要进行相应处理变换,这个我们后面在说。
- 填充的字符可以覆盖,但是’+‘,’-‘,’|‘不能。这个我们从样列1看出来
- 输入的坐标大小是表定的,也就是x1可能小于x2,有可能大于,y1,y2同理
由于本题对于第二种操作需要将输入的坐标及其周围都需要进行填充,所以我们需要用到BFS,或者DFS
BFS(广度优先搜索)
BFS算法常用于解决最短路问题,会用到数据结构队列(先进先出):
push 从队尾插入
pop 从队头弹出
front 返回队头元素
back 返回队尾元素
DFS(深度优先搜索)
DFS大法好,爆搜,非常建议大家多练习,个人感觉确实不好理解,应该多练就好了。
具体如何实现呢?
我最开始写的代码如下:
我最开始直接对坐标不做特殊处理按照,按题目要求来写
具体如下图,假设输入的点是(2,2),则实际要操作的坐标应该是(m-2-1,n-2-1)
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int n,m,q;
const int N=110;
char g[N][N];
typedef pair<int,int>PII;//用于存储坐标
queue<PII>p;
bool st[N][N];
void bfs(int x,int y,char s)
{
p.push({x,y});
st[x][y]=1;
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
while(!p.empty())
{
auto t = p.front();
p.pop();
for(int i=0;i<4;i++){
int x1=t.first+dx[i],y1=t.second+dy[i];
if(x1>=0&& x1<m && y1>=0 && y1<n && g[x1][y1]!='-' && g[x1][y1]!='|' && g[x1][y1]!='+' && !st[x1][y1])
{
st[x1][y1]=1;
g[x1][y1]=s;
p.push({x1,y1});
}
}
}
}
int main()
{
cin>>n>>m>>q;
for (int i=0;i<m;i++){//读入时也需要注意,m作为行,n作为列
for(int j=0;j<n;j++){
g[i][j]='.';
}
}
while(q--)
{
int op;
cin>>op;
if(op==0)//画段操作,若x1=x2,画竖直线,否则画水平线
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
if(x1>x2) swap(x1,x2);
if(y1>y2) swap(y1,y2);
if(x1==x2)//x相等,操作i
{
//int x=n-x1-1;
for(int j=m-y2-1;j<=m-y1-1;j++)
{
if(g[j][n-x1-1]=='-')
g[j][n-x1-1]='+';
else if(g[j][n-x1-1]!='+')
g[j][n-x1-1]='|';
}
}
else
{
int y=m-y1-1;//y相等,操作列(j),
for(int i=n-x2-1;i<=n-x1-1;i++)
{
if(g[y][i]=='|')
g[y][i]='+';
else if(g[y][i]!='+')
g[y][i]='-';
}
}
}
else
{
int x,y;
cin>>x>>y;
char s;
cin>>s;
memset(st,0,sizeof(st));
bfs(m-y-1,n-x-1,s);
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
cout<<g[i][j];
}
cout<<endl;
}
return 0;
}
写完这一版代码,一运行样列都过不了,找了挺久bug,没看出有什么大问题,Acwing一交,果然WA,一个点都过不了,不信邪的我交了一发官网,居然能过50%!!!
这件事告诉我们,目前看来,我们写大模拟是很正确,即便样列都过不了,我们一交还是能拿一半的分!!!
经过一系列的检查,我发现原来我在进入bfs之前,没有把当前点填充,也就是少了
void bfs(int x,int y,char s)
{
p.push({x,y});
st[x][y]=1;
g[x][y]=s;//少了这步,害,这就是太久没刷题要付出的代价
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
......
修改后自己的代码
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int n,m,q;
const int N=110;
char g[N][N];
typedef pair<int,int>PII;
queue<PII>p;
bool st[N][N];
void bfs(int x,int y,char s)
{
p.push({x,y});
st[x][y]=1;
g[x][y]=s;
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
while(!p.empty())
{
auto t = p.front();
p.pop();
for(int i=0;i<4;i++){
int x1=t.first+dx[i],y1=t.second+dy[i];
if(x1>=0&& x1<m && y1>=0 && y1<n && g[x1][y1]!='-' && g[x1][y1]!='|' && g[x1][y1]!='+' && !st[x1][y1])
{
st[x1][y1]=1;
g[x1][y1]=s;
p.push({x1,y1});
}
}
}
}
int main()
{
cin>>n>>m>>q;
for (int i=0;i<m;i++){
for(int j=0;j<n;j++){
g[i][j]='.';
}
}
while(q--)
{
int op;
cin>>op;
if(op==0)//画段操作,x1=x2,画竖直线,否则画水平线
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
if(x1>x2) swap(x1,x2);
if(y1>y2) swap(y1,y2);
if(x1==x2)//y相等,操作列(j),x相等,操作i
{
//int x=n-x1-1;
for(int j=m-y2-1;j<=m-y1-1;j++)
{
if(g[j][n-x1-1]=='-')
g[j][n-x1-1]='+';
else if(g[j][n-x1-1]!='+')
g[j][n-x1-1]='|';
}
}
else
{
int y=m-y1-1;
for(int i=n-x2-1;i<=n-x1-1;i++)
{
if(g[y][i]=='|')
g[y][i]='+';
else if(g[y][i]!='+')
g[y][i]='-';
}
}
}
else
{
int x,y;
cin>>x>>y;
char s;
cin>>s;
memset(st,0,sizeof(st));
bfs(m-y-1,n-x-1,s);
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
cout<<g[i][j];
}
cout<<endl;
}
return 0;
}
改好之后,以为很ok,Acwing上一交,只能过两个点,…
但是咱们在官网一交,能过80%
实在找不到bug,去看了y总的讲解视频,了解了我们可以在输入,并且是在操作的时候就按照我们正常的习惯二维数组坐标去处理,只需要在输出的时候转换为数学坐标系输出即可。
具体操作
//输入的时候
cin>>m>>n>>q;
for (int i=0;i<m;i++){
for(int j=0;j<n;j++){
g[i][j]='.';
}
}
//输出的时候
for(int i=n-1;i>=0;i--){
for(int j=0;j<m;j++){
cout<<g[j][i];
}
cout<<endl;
}
嗯,…理解了好久,没明白为什么不是先输出(m-1,0)这个点,而是先输出(0,n-1),大家有知道滴请在评论区里讲解下,我必三连!!!!
然后改成了这样输入输出与处理。
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
int n,m,q;
const int N=110;
char g[N][N];
typedef pair<int,int>PII;
queue<PII>p;
bool st[N][N];
void bfs(int x,int y,char s)
{
p.push({x,y});
st[x][y]=1;
g[x][y]=s;
int dx[]={-1,0,1,0},dy[]={0,1,0,-1};
while(!p.empty())
{
auto t = p.front();
p.pop();
for (int i = 0; i < 4; i ++ )
{
int a = t.first + dx[i], b = t.second + dy[i];
if (a >= 0 && a < m && b >= 0 && b < n && !st[a][b])
{
if (g[a][b] == '-' || g[a][b] == '|' || g[a][b] == '+') continue;
g[a][b]=s;
st[a][b]=1;
p.push({a,b});
}
}
}
}
int main()
{
cin>>m>>n>>q;
for (int i=0;i<m;i++){
for(int j=0;j<n;j++){
g[i][j]='.';
}
}
while(q--)
{
int op;
cin>>op;
if(op==0)//画段操作,x1=x2,画竖直线,否则画水平线
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
if(x1>x2) swap(x1,x2);
if(y1>y2) swap(y1,y2);
if(x1==x2)//y相等,操作列(j),x相等,操作i
{
//int x=n-x1-1;
for(int j=y1;j<=y2;j++)
{
if(g[x1][j]=='-')
g[x1][j]='+';
else if(g[x1][j]!='+')
g[x1][j]='|';
}
}
else
{
//int y=m-y1-1;
for(int i=x1;i<=x2;i++)
{
if(g[i][y1]=='|')
g[i][y1]='+';
else if(g[i][y1]!='+')
g[i][y1]='-';
}
}
}
else
{
int x,y;
cin>>x>>y;
char s;
cin>>s;
memset(st,0,sizeof(st));
bfs(x,y,s);
}
}
for(int i=n-1;i>=0;i--){
for(int j=0;j<m;j++){
cout<<g[j][i];
}
cout<<endl;
}
return 0;
}
改成了这样后,Acwing和官网都能过
这题也能用DFS写,上y总代码(不得不说,DFS大法好,学到了):
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m, Q;
char g[N][N];
bool st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(int x, int y, char c)
{
st[x][y] = true;
g[x][y] = c;
for (int i = 0; i < 4; i ++ )
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < m && b >= 0 && b < n && !st[a][b])
{
if (g[a][b] == '-' || g[a][b] == '|' || g[a][b] == '+') continue;
dfs(a, b, c);
}
}
}
int main()
{
cin >> m >> n >> Q;
for (int i = 0; i < m; i ++ )
for (int j = 0; j < n; j ++ )
g[i][j] = '.';
while (Q -- )
{
int op;
cin >> op;
if (op == 0)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (x1 > x2) swap(x1, x2);
if (y1 > y2) swap(y1, y2);
char c = '-', d = '|';
if (x1 == x2) swap(c, d);
for (int i = x1; i <= x2; i ++ )//y总把x相对和y相等两种情况合并了
for (int j = y1; j <= y2; j ++ )
{
auto& t = g[i][j];
if (t == d || t == '+') t = '+';
else t = c;
}
}
else
{
int x, y;
char c;
cin >> x >> y >> c;
memset(st, 0, sizeof st);
dfs(x, y, c);
}
}
for (int i = n - 1; i >= 0; i -- )
{
for (int j = 0; j < m; j ++ )
cout << g[j][i];
cout << endl;
}
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/875128/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
欢迎大家在评论区讨论指出我Bug啥的啦!
总结:我们一定要相信自己,敢于写!,写了不一定有分,但是不写一定没分!!!