《程序設計語言綜合設計》第六周上機練習


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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM