《程序设计语言综合设计》第六周上机练习

3 括号匹配调整

如果通过插入“ +”和“ 1”可以从中得到格式正确的数学表达式,则将带括号的序列称为正确的。
例如,序列 "(())()","()"和 "(()(()))"是正确的,而")(","(()))("和"(()" 不是。
定义重新排序操作:选择括号序列的任意连续子段(子字符串),然后以任意方式对其中的所有字符进行重新排序。当重新排序的子段的长度为t时,重新排序操作需要耗时t秒。
例如,对于“))((”,他可以选择子字符串“)(”并重新排序“)()(”(此操作将花费2秒)。
不难看出,重新排序操作不会改变左括号和右括号的数量。
现在,LD想花费最少的时间,通过任意次数(可能为零)执行重新排序操作来使括号序列变成正确的。

输入格式:

第一行包含一个整数n(1≤n≤1e6),表示序列的长度;
第二行包含一个长度为n的字符串,仅由字符‘(’和‘)’组成。

输出格式:

输出一个整数,表示使括号序列正确的最小秒数;如果不可能实现,则输出-1。

输入样例:

8
))((())(

输出样例:

6

Accepted

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
using namespace std;
stack<char>s;
/*首先对于括号的问题很容易想到栈,那么我们对于每个左括号采取的决策是栈顶的左括号能够匹配就立即匹配。因为如果栈顶的括号不匹配,其他的括号也就没办法匹配。栈顶括号匹配的越晚,已经构造的子序列就越长。根据题意可知“当重新排序的子段的长度为t时,重新排序操作需要耗时t秒”,因此如果我们构造的子序列越长,我们所需要花的时间也就会越长*/
int main(){
    int i;
    int n;
    char ch[1000005];
    int ans=0;
    cin >> n ;
   // getchar();
    cin >>ch;
   // cout << ch;
    for(i=0;i<n;i++){
        if(!s.empty()&&ch[i]==')'&&s.top()=='('){
            s.pop();
        }
        else if(!s.empty()&&ch[i]=='('&&s.top()==')'){
           ans+=2;
            s.pop();
        }
        else s.push(ch[i]);
    }
   if(!s.empty()) cout <<"-1"<<endl;
   else cout << ans << endl;
    return 0;
}

但是我发现,如果我将ifelse if条件中!s.empty()放在&&后面就会出现段错误,这是为什么呢?
这是因为且(`&&``)的条件判断是前一个条件成立再判断后一个条件,如果栈为空,那么后一个条件判断放前面就无法进行。

4 奇怪的电梯

c大楼有一个一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼 (1≤i≤N) 上有一个数字Ki(0≤Ki≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3,3,1,2,5 代表了Ki (K1=3,K2=3,…),在1楼,按“上”可以到4楼,按“下”是不起作用的,因为没有−2楼。那么,从A楼到B楼至少要按几次按钮呢?

输入格式:

第一行包含3个用空格隔开的正整数,分别表示N,A,B (1≤N≤200,1≤A,B≤N) 。 第二行包含N 个用空格隔开的非负整数,表示Ki 。

输出格式:

输出共一行,即最少按键次数,若无法到达,则输出 −1 。

输入样例:

5 1 5
3 3 1 2 5

输出样例:

3

Accepted

参考来源

首先码一下基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)也就是说深搜常常与递归函数或者栈结合使用,而广搜主要和队列结合使用。
(1)深度优先搜索(DFS)
当然,这题如果我们当当用深搜的话,最终答案会发现超时。所以这就需要我们进行剪枝。代码如下:

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <queue>
using namespace std;
int n,a,b;
int i;
int ans=99999999;
int sum=0;
int k[1000];
int flag[1000];
void DFS(int now_floor,int sum){//now代表现在的楼层,sum代表按按钮的次数
    if (now_floor==b) ans=min(ans,sum);
    if(sum>ans) return;//一个剪枝,属于最优性剪枝,剪枝的含义:如果当前的sum大于答案,那么它不可能成为答案。
    flag[now_floor]=1;//记录来过当前楼层
    if(now_floor+k[now_floor]<=n&&flag[now_floor+k[now_floor]]==0) {
        DFS(now_floor+k[now_floor],sum+1);
    }
    if(now_floor-k[now_floor]>=1&&flag[now_floor-k[now_floor]]==0) {
           DFS(now_floor-k[now_floor],sum+1);
       }
    flag[now_floor]=0;//回溯
}
int main()
{
    cin >> n >> a >> b;
    for(i=1;i<=n;i++)
        cin >> k[i];
    flag[a]=1;
    DFS(a,sum);
    if(ans==99999999) cout <<"-1";
    else cout <<ans;
    return 0;
}

(2)广度优先搜索(BFS)
这里转载了洛谷上大佬的做法:

#include <iostream>
#include <queue>
using namespace std;

/************************************************************
广度优先搜索算法的基本思想:
1、对于初始状态入队,设置初始状态为已访问
2、如果队列不为空时,出队队头元素,否则跳到第5步
3、检查出队的元素是否为最终解,如果是则跳到第5步。
4、对于出队的元素,检查所有相邻状态,如果有效并且未访问,则将
   所有有效的相邻状态进行入队,并且设置这些状态为已访问,然后
   跳到第2步重复执行
5、检查最后出队的元素是否为最终解,如果是输出结果,否则说明无解

广度优先搜索是借助于队列这种数据结构进行搜索的,队列的特点是先
进先出(FIFO),通过包含queue这个队列模板头文件,就可以利用c++
的队列模板定义自己的队列了,队列的操作非常简单,主要有以下几个:
q.push() 入队操作
q.front() 取队头元素
q.pop() 队头元素出队
q.size() 获取队列的元素个数
q.empty() 判断队列是否为空,为空返回true,不为空返回false

广度优先搜索算法的关键是要搞清楚求解过程中每一步的相邻状态有哪些,
每个状态需要记录什么信息,在搜索过程中如何标记这些状态为已访问。

在本题中,相邻状态为当前所在楼层通过按向上或向下按钮所能到达的楼
层,每个状态要记录的信息包括楼层编号和按按钮的次数。
*************************************************************/

//定义队列元素的类型,QElement为结构类型,使用typedef可以定义一个新的类型名称,在程序中QElement就像int、float一样,作为一个数据类型的名称使用
typedef struct {
    int floor;  //当前所处的楼层编号
    int pushcount;  //到达该楼层所经历的步数(按按钮次数)
} QElement;
queue<QElement> q; //定义元素类型为QElement的队列q
int n,a,b;
int s[1000];     //数组s记录每个楼层按按钮后能上下的楼层数
int t[1000]={0}; //数组t记录各个楼层是否已经到达过(已访问过)
int main()
{
    QElement e1,e2;
    int i;
    cin >> n >> a >> b;
    for (i=1; i<=n; i++) cin >> s[i];
    e1.floor=a;
    e1.pushcount=0;
    q.push(e1);  //初始状态入队:当前楼层为a,按按钮次数为0
    t[a]=1;  //记录当前楼层已访问过
    while (!q.empty())  //当队列不为空时,继续宽度优先搜索
    {
        e2=q.front();   //获取队头元素
        q.pop();  //队头元素出队(注意:c++的队列模板类中,获取队头元素并不会将该元素从队列中删除,需要使用pop函数删除该元素)
        if (e2.floor==b) break;  //检查当前状态的楼层编号是否为b,是则说明已经找到最终解,跳出循环
        i=e2.floor+s[e2.floor];  //按向上按钮后能够到达的楼层
        if (i<=n && t[i]==0)  //如果按向上按钮能到达的楼层有效并且未访问过该楼层
        {
            e1.floor=i;
            e1.pushcount=e2.pushcount+1;
            q.push(e1);
            t[i]=1;  //设该楼层为已访问过
        }
        i=e2.floor-s[e2.floor];  //按向下按钮后能够到达的楼层
        if (i>=1 && t[i]==0)  //如果按向下按钮能到达的楼层有效并且未访问过该楼层
        {
            e1.floor=i;
            e1.pushcount=e2.pushcount+1;
            q.push(e1);
            t[i]=1;  //设该楼层为已访问过
        }
    }
   //如果当前楼层为b,输出按按钮次数,否则无解(输出-1)
    if (e2.floor==b) cout << e2.pushcount;
    else cout << -1;
}

5 168

汉堡包在大街上大摇大摆的走着,看着手机上一道难倒数万人的小学数学题:
1 + 1 = 0
1 + 6 = 1
6 + 6 = 2
8 + 1 = 2
8 + 6 = 3
汉堡包看完之后发现上面这些加法的答案就是看1,6,8中圈圈的个数嘛!
突然之间,所有大厦上的LED屏幕上的广告全部变成数字1,6,8三个数字的随机闪现。
现给你一块n*m的LED屏幕,上面有且仅有一个数字(1,6,or 8),请你输出你看见的那个字母。

输入格式:

第一行输入两个整数n,m(2<= m, n <= 1000);
接下来n行,每行由m个数字0和1组成,其中1表示数字1,6,8的组成部分。

输出格式:

输出一个整数,代表图形表示的数字。

输入样例:

7 7
0 0 0 0 0 0 0
0 0 1 1 1 0 0
0 0 1 0 1 0 0
0 0 1 1 1 0 0
0 0 1 0 1 0 0
0 0 1 1 1 0 0
0 0 0 0 0 0 0

输出样例:

8

Accepted

这一题的思路主要是我们观察1的数量。如果图形是“1”那么其中1的数量都是相同的,我们记为flag=1
,以此类推,如果图形是“8”那么其中1的数量有两种不同的,我们记为flag=2,如果图形是“6”那么其中1的数量有三种不同的,我们记为flag=3,即可得出答案。
具体我们看下面的图来理解什么是1的数量:

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <queue>
using namespace std;
int main(){
    int i,j,k=1;
    int n,m;
    int num[1010]={0};
    int num_cmp=0;
    int flag=1;
    int led[1005][1005];
    cin >> n >> m;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            cin >> led[i][j];
            if(led[i][j]==1)  num[k]++;
        }
        if(num[k]!=0)k++;
    }
    num_cmp=num[k-1];
    for(i=k-1;i>0;i--){
        if(num[i]<num_cmp) {
            flag++;
            num_cmp=num[i];
        }
    }
    if(flag==1) cout <<"1"<<endl;
    else if(flag==2) cout <<"8"<<endl;
    else  cout <<"6"<<endl;
    return 0;
}

6 寻宝

奕哥今天玩到一款寻宝游戏,地图是一个n*m的矩阵,其中分布着一些宝藏,每个宝藏具有一定的价值,奕哥只能拿走其中一个宝藏。奕哥起始在a行b列。奕哥可以向相邻的一格移动,但不能走出地图外。奕哥初始体力值X,移动一格需要消耗体力值为1。体力耗尽后奕哥无法继续移动。地图中有一些障碍物,奕哥无法移动到障碍物上。奕哥想知道他能拿到的最具价值的宝藏是哪一个。

输入格式:

第一行包含5个整数n,m,a,b,X。n,m分别表示地图行数,列数;a,b表示奕哥起始位置在a行b列;X表示奕哥的体力。( 1<=n,m<=1000, 1<=a<=n, 1<=b<=m, 0<=X<=1e10)
接下来n行,每行m个整数。第i行第j个整数为Aij (-1<=Aij<=1e9)。若Aij=-1,则表示第i行第j列有障碍物;否则表示该位置有一个价值为Aij的宝物。保证起始位置没有障碍物。

输出格式:

奕哥能拿到价值最高的宝藏的价值。

输入样例:

3 3 1 1 3
0 -1 10
3 5 7
-1 8 15

输出样例:

8

Wrong Answer

一开始拿到这个题目觉得只用用dfs就可以解决问题,但是发现有几个点超时了

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <queue>
using namespace std;
int n,m,a,b,x;
int p[1005][1005];
int ans=0;
void DFS(int x0,int y0,int x){
    if(p[x0][y0]==-1||x==0||x0>n||x0<1||y0>m||y0<1) return ;
    ans=max(ans,p[x0][y0]);
    DFS(x0-1,y0,x-1);
    DFS(x0+1,y0,x-1);
    DFS(x0,y0-1,x-1);
    DFS(x0,y0+1,x-1);
}
int main()
{
    
    int i,j;
    cin >> n >> m >> a >> b >> x;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            cin >> p[i][j];
        }
    }
    DFS(a,b,x+1);
    cout << ans << endl;
    return 0;
}

然后我就去网上查了一些博客,上面解答说如果dfs超时了,我们可以采用剪枝的方法。

如何剪枝?
1.最优化剪枝:如果当前已经找到的最优路径长度为L ,那么在继续搜索的过程中,总长度已经大于等于L的走法,就可以直接放弃,不用走到底了。
2.可行性剪枝:如果当前到达城市的路费已大于k,或者等于k且没有到达终点,就可以直接放弃。
很显然,由于我们不知道宝藏最大
然后,我就尝试找出宝藏中最大的那个值,然后如果if(ans==max_) return;但还是会发现超时。这时候,我询问了一个同学,才知道我们可以标记一下已走过的点,如果这个点已经走过了,那么我们就不需要重新再走,具体代码如下。

Accepted

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <queue>
using namespace std;
int n,m,a,b,x;
int p[1005][1005];
int flag[1005][1005]={0};
int ans=0;
void DFS(int x0,int y0,int x){
    if(p[x0][y0]==-1||x==0||x0>n||x0<1||y0>m||y0<1||flag[x0][y0]) return ;
    ans=max(ans,p[x0][y0]);
    flag[x0][y0]=1;
    DFS(x0-1,y0,x-1);
    DFS(x0+1,y0,x-1);
    DFS(x0,y0-1,x-1);
    DFS(x0,y0+1,x-1);
}
int main(){
    int i,j;
    cin >> n >> m >> a >> b >> x;
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            cin >> p[i][j];
        }
    }
    DFS(a,b,x+1);
    cout << ans << endl;
    return 0;
}

7 龙舌兰酒吧

有一个大小为n*m的矩形小镇,城镇上有房屋(“#”表示,无法通过),有空地(“.”表示,可通行),每次移动只能朝上下左右四个方向,且需花费1单位时间。
一天,二乔和承太郎约定在龙舌兰酒吧见面,两人同时从各自所在位置向酒吧出发。请问最少需要过多少时间他们才能在酒吧碰面。
地图上P表示二乔的位置,W表示承太郎的位置,B表示酒吧的位置。

输入格式:

第一行包含两个整数n,m,表示城镇的大小。(1<=m,n<=1000)。
接下来n行,每行m个字符,表示小镇的情况。

输出格式:

输出两人在酒吧碰面的最少花费时间,如果无法在酒吧碰面则输出-1。

输入样例:

5 4
.W.#
P#..
....
B..#
#...

输出样例:

4

Wrong Answer

这题我一开始用了广搜然后由于他们是同时出发,取他们两个中花时间最长的一个时间就可以了,但是会发现超时了。

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <queue>
using namespace std;
int n,m;
char p[1005][1005];
int flag[1005][1005]={0};
int ans1=9999,ans2=9999;
int xp,yp,xw,yw;
void DFS1(int x0,int y0,int sum){
    if(sum>=ans1||x0<1||x0>n||y0<1||y0>m||p[x0][y0]=='#') return;
    if(p[x0][y0]=='B'){
        ans1=min(ans1,sum);
    }
    DFS1(x0-1,y0,sum+1);
    DFS1(x0+1,y0,sum+1);
    DFS1(x0,y0-1,sum+1);
    DFS1(x0,y0+1,sum+1);
}
void DFS2(int x0,int y0,int sum){
    if(sum>=ans2||x0<1||x0>n||y0<1||y0>m||p[x0][y0]=='#') return;
    if(p[x0][y0]=='B'){
        ans2=min(ans2,sum);
    }
    DFS2(x0-1,y0,sum+1);
    DFS2(x0+1,y0,sum+1);
    DFS2(x0,y0-1,sum+1);
    DFS2(x0,y0+1,sum+1);
}
int main(){
    int i,j;
    cin >> n >> m ;
     getchar();
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            cin >> p[i][j];
            if(p[i][j]=='P') {
                xp=i;
                yp=j;
            }
            if(p[i][j]=='W') {
                xw=i;
                yw=j;
            }
        }
         getchar();
    }
    DFS1(xp,yp,0);
    DFS2(xw,yw,0);
   if(ans1!=9999&&ans2!=9999) cout << max(ans1,ans2)<< endl;
   else cout <<"-1"<<endl;
    return 0;
}

Accepted

所以这题需采用BFS。(参考:BFS求解迷宫的最短路径问题)

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cmath>
#include <stack>
#include <queue>
#define P pair<int, int>//pair是将2个数据组合成一组数据
using namespace std;
int n,m;
char p[1005][1005];
struct person{
    int x0;
    int y0;
}p1,w1;
int xb=0,yb=0;
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};  //表示x和y可以移动的四个方向
const int exmp=999999;
int ansp=0,answ=0;
int bfs(person a){
    queue<P> q_new;
    int i,j;
    int dis[1005][1005];  //保存起点到各点最短距离
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            dis[i][j]=exmp;   //将起点到各点的距离初始化为无穷大,表示为到达
        }
    }
    q_new.push(P(a.x0,a.y0));
    dis[a.x0][a.y0]=0;  //从起点出发将距离设为0 ,并放入队列
    //不断循环直到队列的长度为0
    while(q_new.size()){
        //取出队首元素
        P r=q_new.front();
        q_new.pop();
         //如果取出的状态是终点,则结束搜索
       if(r.first==xb&&r.second==yb) break;
        //四个方向的循环
        for(i=0;i<4;i++){
            //移动之后的坐标记为(dx,dy)
            int dx=r.first+dir[i][0];
            int dy=r.second+dir[i][1];
            //判断是否已经访问过,如果dis[dx][dy]不为exmp即为已经访问过
            if(dx>0&&dx<=n&&dy>0&&dy<=m&&p[dx][dy]!='#'&&dis[dx][dy]==exmp){
                //可以移动的话,则加入到队列,并且该位置的距离确定为到r的距离加1
                q_new.push(P(dx,dy));
                dis[dx][dy]=dis[r.first][r.second]+1;
            }
        }
    }
    return dis[xb][yb];
}


int main(){
    int i,j;
    cin >> n >> m ;
     getchar();
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            cin >> p[i][j];
            if(p[i][j]=='P') {
                p1.x0=i;
                p1.y0=j;
            }
            if(p[i][j]=='W') {
                w1.x0=i;
                w1.y0=j;
            }
            if(p[i][j]=='B') {
                xb=i;
                yb=j;
            }
        }
         getchar();
    }
    ansp=bfs(p1);
    answ=bfs(w1);
    if(ansp==exmp||answ==exmp) cout <<"-1"<<endl;
    else cout <<max(ansp,answ)<<endl;
  
    return 0;
}

posted @ 2020-03-29 12:12  平和Kenn  阅读(1594)  评论(6编辑  收藏  举报