1. 程式人生 > >列舉和bfs練習

列舉和bfs練習

今天講的是廣度優先搜尋,作為大二的稍微有一點基礎。上午在練資料書上的列舉,下午練vjudge上的bfs,然後就一直在和程式碼死磕。

生理週期

OpenJ_Bailian 2997 POJ 1006

人生來就有三個生理週期,分別為體力、感情和智力週期,它們的週期長度為 23 天、28 天和 33 天。每一個週期中有一天是高峰。對於每個人,我們想知道何時三個高峰落在同一天。
對於每個週期,我們會給出從當前年份的第一天開始,到出現高峰的天數(不一定是第一次高峰出現的時間)。你的任務是給定一個從當年第一天開始數的天數,輸出從給定時間開始(不包括給定時間)下一次三個高峰落在同一天的時間(距給定時間的天數)。例如:給定
時間為 10,下次出現三個高峰同天的時間是 12,則輸出 2(注意這裡不是 3)。
輸入資料
輸入四個整數:p, e, i和 d。 p, e, i 分別表示體力、情感和智力高峰出現的時間(時間從當年的第一天開始計算)。d 是給定的時間,可能小於 p, e, 或 i。 所有給定時間是非負的並且小於 365, 所求的時間小於等於 21252。
輸出要求
從給定時間起,下一次三個高峰同天的時間(距離給定時間的天數)。
輸入樣例
0 0 0 0
0 0 0 100
5 20 34 325
4 5 6 7
283 102 23 320
203 301 203 40
-1 -1 -1 -1
輸出樣例
Case 1: the next triple peak occurs in 21252 days.
Case 2: the next triple peak occurs in 21152 days.
Case 3: the next triple peak occurs in 19575 days.
Case 4: the next triple peak occurs in 16994 days.
Case 5: the next triple peak occurs in 8910 days.
Case 6: the next triple peak occurs in 10789 days.

OpenJ_Bailian 2997 這個平臺上兩份程式碼都顯示Output Limit Exceeded 但是在POJ 1006上第二份AC了
在未看解析之前,就直接蠻力窮舉法,因為最多可迴圈21252次,大約是2的15次方,然後肯定會超時
改進之後的程式碼是先找出第一個能夠被第一個週期整除的時間,然後在此時間上累加找出能夠被第二個週期整除的時間,之後再每次增加第一個週期和第二個週期的乘積,保證在達到前兩個高峰的同時能夠被第三個週期整除,進而達到第三個高峰

#include<stdio.h>
int tp = 23;
int te = 28;
int ti = 33
; int main(){ int p,e,i,d,t,no = 1; while(scanf("%d%d%d%d",&p,&e,&i,&d) && p>=0 && e>=0 && i>=0 && d>=0){ for(t = d+1;t <= 21252;t++){ if(((t-p)%tp == 0)&&((t-e)%te == 0)&&((t-i)%ti == 0)){ printf
("Case %d: the next triple peak occurs in %d days.\n",no++,t-d); break; } } } return 0; }
//AC
#include<stdio.h>
int tp = 23;
int te = 28;
int ti = 33;

int main(){
    int p,e,i,d,t,no = 1;
    while(scanf("%d%d%d%d",&p,&e,&i,&d) && p>=0 && e>=0 && i>=0 && d>=0){
        for(t = d+1;t <= 21252;t++){  //Attention 不包括給定時間
            if((t-p)%tp == 0){
                break;
            }
        }
        for(;t <= 21252;t += tp){
            if((t-e)%te == 0){
                break;
            }
        }
        for(;t <= 21252;t += tp*te){
            if((t-i)%ti == 0){
                break;
            }
        }
        printf("Case %d: the next triple peak occurs in %d days.\n",no++,t-d);
    }
    return 0;
}

稱硬幣

POJ 1013 OpenJ_Bailian 2692

賽利有 12 枚銀幣。其中有 11 枚真幣和 1 枚假幣。假幣看起來和真幣沒有區別,但是重量不同。但賽利不知道假幣比真幣輕還是重。於是他向朋友借了一架天平。朋友希望賽利稱三次就能找出假幣並且確定假幣是輕是重。例如:如果賽利用天平稱兩枚硬幣,發現天平平
衡,說明兩枚都是真的。如果賽利用一枚真幣與另一枚銀幣比較,發現它比真幣輕或重,說明它是假幣。經過精心安排每次的稱量,賽利保證在稱三次後確定假幣。
輸入資料
輸入有三行,每行表示一次稱量的結果。賽利事先將銀幣標號為 A-L。每次稱量的結果用三個以空格隔開的字串表示:天平左邊放置的硬幣 天平右邊放置的硬幣 平衡狀態。其中平衡狀態用”up”, “down”, 或 “even”表示, 分別為右端高、右端低和平衡。天平左右的硬幣數總是相等的。
輸出要求:
輸出哪一個標號的銀幣是假幣,並說明它比真幣輕還是重。
輸入樣例
1
ABCD EFGH even
ABCI EFJK up
ABIJ EFGH even
輸出樣例
K is the counterfeit coin and it is light.

自己最開始的想法也就是先找出天平狀態為“even”的一次稱量,可以判斷此時天平兩端都是真幣,然後從餘下的稱量結果中,找出未被證實過(即出現在平衡天平兩端的硬幣編號中)的硬幣即是假幣,然後再根據假幣所在的左右和天平右端的輕重判斷是輕還是重
顯然這種想法可行性不高,反正我程式碼提交的時候顯示答案錯誤。
以下是解析:答案以用兩個變量表示:x假幣的標號、w假幣是比真幣輕還是比真幣重。x 共有 12 種猜測;w有 2 種猜測。根據賽利設計的稱量方案,(x,w )的 24 種猜測中,只有唯一的猜測與三組稱量資料都不矛盾。因此,如果猜測(x,w )滿足下列條件,這個猜測就是要找的答案:
在稱量結果為”even” 的天平兩邊,沒有出現 x ;
如果 w 表示假幣比真幣輕,則在稱量結果為”up” 的天平右邊一定出現 x、在稱量結果為”down” 的天平左邊一定出現 x
如果 w 表示假幣比真幣重,則在稱量結果為”up” 的天平左邊一定出現 x、在稱量結果為”down” 的天平右邊一定出現 x
這裡新學習到一個函式:strchr() 函式是用來判斷一個字元是否在一個字串中出現

#include<stdio.h>
#include<string.h>
char coin[13] = {"ABCDEFGHIJKL"};
int flag[12] = {0};

int main(){
    int t,i,j;
    char right[6],left[6],s[4];
    scanf("%d",&t);
    while(t--){
        for(i = 0;i < 3;i++){
            scanf("%s%s%s",right,left,s);
            if(s == "even"){    //天平如果平衡則說明此事天平兩端都是真幣
                for(j = 0;j < strlen(right);j++){
                    flag[right[j]-'A'] = 1;
                    flag[left[j]-'A'] = 1;
                }
            }
        }
        for(i = 0;i < 3;i++){
            for(j = 0;j < strlen(right);j++){
                if(flag[right[j]-'A'] == 0){
                    printf("%c is the counterfeit coin and it is %s.\n",right[j],s == "up"?"light":"heavy");
                }
                if(flag[left[j]-'A'] == 0){
                    printf("%c is the counterfeit coin and it is %s.\n",left[j],right[j],s == "down"?"light":"heavy");
                }
            }
        }
    }
    return 0;
}

//Wrong Answer
#include<stdio.h>
#include<string.h>
char right[3][7],left[3][7],s[3][5];

bool isLight(char c){   //判斷硬幣 x 是否為輕
    int i;
    for(i = 0;i < 3;i++){
        switch(s[i][0]){
        case'e':
            if(strchr(right[i],c) != NULL || strchr(left[i],c) != NULL){  //strchr() 函式是用來判斷一個字元是否在一個字串中出現
                return false;
            }
            break;
        case'u':
            if(strchr(right[i],c) == NULL){  
                return false;
            }
            break;
        case'd':
            if(strchr(left[i],c) == NULL){  
                return false;
            }
            break;
        }
    }
    return true;
}

bool isHeavy(char c){      //判斷硬幣 x 是否為重
    int i;
    for(i = 0;i < 3;i++){
        switch(s[i][0]){
        case'e':
            if(strchr(right[i],c) != NULL || strchr(left[i],c) != NULL){  //strchr() 函式是用來判斷一個字元是否在一個字串中出現
                return false;
            }
            break;
        case'd':
            if(strchr(right[i],c) == NULL){  
                return false;
            }
            break;
        case'u':
            if(strchr(left[i],c) == NULL){  
                return false;
            }
            break;
        }
    }
    return true;
}

int main(){
    int t,i;
    char c;
    scanf("%d",&t);
    while(t--){
        for(i = 0;i < 3;i++){
            scanf("%s %s %s",right[i],left[i],s[i]);
        }
        for(c = 'A';c <= 'L';c++){
            if(isLight(c)){
                printf("%c is the counterfeit coin and it is light.\n",c);
                break;
            }
            if(isHeavy(c)){
                printf("%c is the counterfeit coin and it is light.\n",c);
                break;
            }
        }
    }
    return 0;
}

完美立方

a3= b3 + c3 + d3為完美立方等式。例如 123= 63+ 83+ 103。編寫一個程式,對任給的正整數 N (N≤100),尋找所有的四元組(a, b, c, d),使得 a3 = b3 + c3 + d3,其中 1<a,b,c,dN
輸入資料
正整數 N (N≤100)
輸出要求
每行輸出一個完美立方,按照 a 的值,從小到大依次輸出。當兩個完美立方等式中 a 的值相同,則依次按照 b、c、d 進行非降升序排列輸出,即 b值小的先輸出、然後 c值小的先輸出、然後 d 值小的先輸出。

輸入樣例
24
輸出樣例
Cube = 6, Triple = (3,4,5)
Cube = 12, Triple = (6,8,10)
Cube = 18, Triple = (2,12,16)
Cube = 18, Triple = (9,12,15)
Cube = 19, Triple = (3,10,18)
Cube = 20, Triple = (7,14,17)
Cube = 24, Triple = (12,16,20)

題目比較簡單,應該注意的幾個問題:1)N的範圍是(1,100】,100的3次方為1000000超出了整型資料可以表示的資料範圍,所以應該用long long 型儲存三次方變數;2)為了避免對一個數多次求立方然後增加時間開銷,可以聯想到資料預處理的字首和思想,預先開一個數組儲存5到N的立方;3)直接用四重迴圈求出所有滿足條件的四元組;4)考慮到b,c,d均大於1,也就是最小都取2,所以a最小從5開始。
1.0程式碼執行超時了,因為多次計算了多個數的立方

//Time Limit Exceeded
#include<stdio.h>
#include<math.h>

int main(){
    int n;
    int a,b,c,d;
    scanf("%d",&n);
    for(a = 5;a <= n;a++){
        for(b = 2;b < a;b++){
            for(c = 2;c < a;c++){
                for(d = 2;d < a;d++){
                    if(pow(a,3)-pow(b,3)-pow(c,3)-pow(d,3) == 0 && b<c && b<d && c<d){
                        printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
                        break;
                    }
                }
            }
        }
    }
    return 0;
}

**2.0程式碼主要是用了字首和思想預先把小於N的整數的立方求出來儲存在陣列中。考慮到需要將資料非降序輸出,就增加了b,c,d的大小判斷(忽然考慮到滿足的條件應該是小於等於。。。不知道程式碼之前怎麼通過了)

//AC 預處理字首和思想
#include<stdio.h>
#include<math.h>

int main(){
    int n,i;
    int a,b,c,d;
    scanf("%d",&n);
    long long r[101];
    for(i = 0;i <= n;i++){
        r[i] = pow(i,3);
    }
    for(a = 5;a <= n;a++){
        for(b = 2;b < a;b++){        //注意迴圈範圍
            for(c = 2;c < a;c++){
                for(d = 2;d < a;d++){
                    if(r[a]-r[b]-r[c]-r[d] == 0 && b<c && b<d && c<d){
                        printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
                        break;
                    }
                }
            }
        }
    }
    return 0;
}

3.0程式碼。將b的迴圈範圍縮小至【2,a),將c的迴圈範圍縮小至【b,a),將d的迴圈範圍縮小至【c,a).

//AC
#include<stdio.h>
#include<math.h>

int main(){
    int n,i;
    int a,b,c,d;
    scanf("%d",&n);
    long long r[101];
    for(i = 0;i <= n;i++){
        r[i] = pow(i,3);
    }
    for(a = 5;a <= n;a++){
        for(b = 2;b < a;b++){
            for(c = b;c < a;c++){
                for(d = c;d < a;d++){
                    if(r[a]-r[b]-r[c]-r[d] == 0){
                        printf("Cube = %d, Triple = (%d,%d,%d)\n",a,b,c,d);
                        break;
                    }
                }
            }
        }
    }
    return 0;
}

這三個現在看上去很簡單的題,弄了一上午,有很多即是看上去簡單的東西可能就僅僅一個點轉不過來也會被一直卡在那裡

Catch That Cow

Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer John has two modes of transportation: walking and teleporting.

  • Walking: FJ can move from any point X to the points X - 1 or X + 1 in a single minute
  • Teleporting: FJ can move from any point X to the point 2 × X in a single minute.

If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?

Input
Line 1: Two space-separated integers: N and K
Output
Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.
Sample Input
5 17
Sample Output
4

題目很簡單,給你兩個x軸上的兩個距離x,y,x每一次可進行如下兩種變換之一:1)x+1或x-1;2)x*2,求最少經過多少次變換x=y
最開始思路走錯了一個方向,認為在 x<<y 的情況下只能將x翻倍增加到接近y的時候再考慮一個一個點地移動,於是有了1.0程式碼.把題目想簡單了,絲毫沒有考慮到這是一道bfs的題(笑xry)

//Error
#include<stdio.h>

int main(){
    int n,k;
    int t = 0;
    //long long  n,k;
    scanf("%d%d",&n,&k);
    while(n != k){
        if(n > k){
            t += n-k;
            break;
        }
        if(k >= 1.5*n+0.5){
            n *= 2;
        }
        else{
            n += 1;
        }
        t++;        
    }
    printf("%d\n",t);
    return 0;
}

2.0程式碼。用了佇列,感覺太久時間沒有接觸隊列了,應該是說資料結構之後就沒有寫過佇列的程式碼,太生疏了,所以就在百度上看了一些基本用法。資料結構學的是自己寫的佇列,注重程式碼底層的實現。但是C++中的queue容器用起來卻非常非常方便!!!
直接將每個x的x+1,x-1,x*2都入佇列,然後依次出佇列,這裡如果考慮到x,y的取值範圍,資料過大佇列的開銷太大,就知道應該記憶體超標了,同時在廣度優先搜尋的時候在掃描完每一層進入到新的一層時計數加一

//Memory Limit Exceeded
#include<iostream>
#include<queue>
using namespace std; //這幾個標頭檔案必不可少

int main(){
    int n,k;
    int t = 0,r;
    //long long n,k;
    cin>>n>>k;
    if(n > k){
        t += n-k;
        cout<<t<<endl;
        return 0;
    }
    queue<int> q;
    q.push(n);
    r = n;  
    while(n != k){
        if(n == r-1){
            t++;
            r -= 1;
        }
        n = q.front();
        q.pop();
        q.push(n-1);
        q.push(n+1);
        q.push(n*2);
        n = q.front();  
    }
    cout<<t<<endl;
    return 0;
}

3.0程式碼。對每一個x的x+1,x-1,x*2都先進行判斷,符合相應的條件再入佇列,並且佇列中的資料是當前的位置和所需要的變換次數,然後依次出佇列。同時增加了一個標誌陣列嗎如果該位置已經入過佇列,那麼第二種情況下到達改點時就不必重複進隊列了。

//AC
#include<iostream>
#include<queue>
using namespace std; 
const int MAX = 100000;

int flag[MAX] = {0};
struct Point{
    int x;
    int t;
};

int main(){
    int n,k;
    //long long n,k;
    cin>>n>>k;
    if(n >= k){
        cout<<n-k<<endl;
        return 0;
    }
    queue<Point> q;
    Point p,ans;
    p.x = n; 
    p.t = 0;
    q.push(p);
    while(!q.empty()){
        p = q.front();
        q.pop();
        if(p.x == k){
            break;
        }
        n = p.x-1;
        if(n >= 0 && n <= MAX && !flag[n]){
            flag[n] = 1;
            ans.t = p.t+1;
            ans.x = n;
            q.push(ans);
        }
        n = p.x+1;
        if(n >= 0 && n <= MAX && !flag[n]){
            flag[n] = 1;
            ans.t = p.t+1;
            ans.x = n;
            q.push(ans);
        }
        n = p.x*2;
        if(n >= 0 && n <= MAX && !flag[n]){
            flag[n] = 1;
            ans.t = p.t+1;
            ans.x = n;
            q.push(ans);
        }
    }
    cout<<p.t<<endl;
    return 0;
}

迷宮問題

定義一個二維陣列:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};

它表示一個迷宮,其中的1表示牆壁,0表示可以走的路,只能橫著走或豎著走,不能斜著走,要求程式設計序找出從左上角到右下角的最短路線。
Input
一個5 × 5的二維陣列,表示一個迷宮。資料保證有唯一解。
Output
左上角到右下角的最短路徑,格式如樣例所示。
Sample Input
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

這道題是在最後大家都快走完的時候,因為自己實在是不甘心還想再和程式碼死磕一會,然後終於改好了然後通過的,顯示“Accept”的時候真的好感動,因為一個晚上倆個小時用了前一個小時在寫程式碼,讓他編譯執行通過,然後後面一個小時就都在除錯程式碼讓它能在網站上通過了,真的改程式碼比寫程式碼要心累啊後多,從邏輯上一步一步走了很多遍,硬是覺得沒有問題。這道題最開始沒有通過的原因是我有一個變數都設為a了,然後再賦值的時候不知不覺就會改變其自身的值,天啦,真的被這一個小毛病坑了好久,所以說還是要有耐心,讓自己的程式碼能夠perfect,哪怕一個再小的問題也會成為你AC的障礙
本來考慮用雙向搜尋的,但考慮到這一道題資料量也不大,就只用了一個佇列來廣度優先搜尋。

#include<iostream>
#include<queue>
using namespace std;
int maze[5][5];
int flag[5][5] = {0};
struct Point{
    int x,y;
};

int main(){
    int i,j;
    queue<Point> q;
    Point a,p;
    for(i = 0;i < 5;i++){
        for(j = 0;j < 5;j++){
            cin>>maze[i][j];
        }
    }
    a.x = a.y = 4;
    q.push(a);
    while((i > 0 || j > 0) && !q.empty()){
        a = q.front();
        q.pop();
        i = a.x+1;
        j = a.y;
        if(i <= 4 && !maze[i][j] && !flag[i][j]){   //下
            flag[i][j] = 1;
            p.x = i;       //死磕了好久,開始都設為a了
            p.y = j;
            q.push(p);
        }
        i = a.x-1;
        j = a.y;
        if(i >= 0 && !maze[i][j] && !flag[i][j]){   //上
            flag[i][j] = 2;
            p.x = i;
            p.y = j;
            q.push(p);
        }
        i = a.x;
        j = a.y+1;
        if(j <= 4 && !maze[i][j] && !flag[i][j]){   //右
            flag[i][j] = 3;
            p.x = i;
            p.y = j;
            q.push(p);
        }
        i = a.x;
        j = a.y-1;
        if(j >= 0 && !maze[i][j] && !flag[i][j]){  //左
            flag[i][j] = 4;
            p.x = i;
            p.y = j;
            q.push(p);
        }
    }
    cout<<"(0, 0)"<<endl;
    i = j = 0;
    while(i != 4 || j != 4){
        if(flag[i][j] == 1){
            i--;
            cout<<"("<<i<<", "<<j<<")"<<endl;
        }
        if(flag[i][j] == 2){
            i++;
            cout<<"("<<i<<", "<<j<<")"<<endl;
        }
        if(flag[i][j] == 3){
            j--;
            cout<<"("<<i<<", "<<j<<")"<<endl;
        }
        if(flag[i][j] == 4){
            j++;
            cout<<"("<<i<<", "<<j<<")"<<endl;
        }
    }
    return 0;
}

小總結:明天繼續死磕dfs和bfs.感覺題目做了一定數量之後,會有一個基本的公式套路,然後遇到類似的提就可以直接套進去。送你一句話:永遠自在如風!