1. 程式人生 > >藍橋杯 演算法訓練 Lift and Throw

藍橋杯 演算法訓練 Lift and Throw

問題描述

  給定一條標有整點(1, 2, 3, …)的射線. 定義兩個點之間的距離為其下標之差的絕對值.
  Laharl, Etna, Flonne一開始在這條射線上不同的三個點, 他們希望其中某個人能夠到達下標最大的點.
  每個角色只能進行下面的3種操作, 且每種操作不能每人不能進行超過一次.
  1.移動一定的距離
  2.把另一個角色高舉過頭
  3.將舉在頭上的角色扔出一段距離
  每個角色有一個movement range引數, 他們只能移動到沒有人的位置, 並且起點和終點的距離不超過movement range.
  如果角色A和另一個角色B距離為1, 並且角色B沒有被別的角色舉起, 那麼A就能舉起B. 同時, B會移動到A的位置,B原來所佔的位置變為沒有人的位置. 被舉起的角色不能進行任何操作, 舉起別人的角色不能移動.同時, 每個角色還有一個throwing range引數, 即他能把舉起的角色扔出的最遠的距離. 注意, 一個角色只能被扔到沒有別的角色佔據的位置. 我們認為一個角色舉起另一個同樣舉起一個角色的角色是允許的. 這種情況下會出現3個人在同一個位置的情況. 根據前面的描述, 這種情況下上面的兩個角色不能進行任何操作, 而最下面的角色可以同時扔出上面的兩個角色. 你的任務是計算這些角色能夠到達的位置的最大下標, 即最大的數字x, 使得存在一個角色能夠到達x.

輸入格式

  輸入共三行, 分別為Laharl, Etna, Floone的資訊.
  每一行有且僅有3個整數, 描述對應角色的初始位置, movement range, throwing range.
  資料保證3個角色的初始位置兩兩不相同且所有的數字都在1到10之間.

輸出格式

  僅有1個整數, 即Laharl, Etna, Flonne之一能到達的最大距離.

樣例輸入

9 3 3
4 3 1
2 3 3

樣例輸出

15

樣例說明

  一開始Laharl在位置9, Etna在位置4, Flonne在位置2.
  首先, Laharl移動到6.
  然後Flonne移動到位置5並且舉起Etna.
  Laharl舉起Flonne將其扔到位置9.
  Flonne把Etna扔到位置12.
  Etna移動到位置15.

題解:

題目已經提示了該題是搜尋題。一開始也是沒有思路的,搜尋了網上的一些程式碼,雖然沒有立刻看懂,但是瞭解到可能先弄懂全排列演算法對解決該題會有幫助。的確如此,在解決了全排列問題後,再看這題就有思路了。具體可以看我上一篇的全排列整理。
理解全排列之後再看這題,就知道這題就是不斷深搜和回溯的過程。
但是僅僅全排列肯定是超時的。這就需要細心地剪枝優化了。如下圖,我第三次提交的時候76分,可以算出全部資料的正確結果,但是計算最大的資料需要10+s,妥妥的超時了。然後通過不斷剪枝,不斷髮現可以優化的地方,從76分到95分,最後優化的程式碼計算最大的資料是需要31ms。可見剪枝對於搜尋的重要性。

這裡寫圖片描述
先來說說深搜的過程,然後再來說剪枝優化。

深搜:

一共有3個人,每個人有3種不同的動作,移動、舉起旁邊的人或扔出舉起的人。所以加起來一共有9種不同的操作。
我們給這9中操作編碼,分別為0~8,每次搜尋都是從這8種不同的操作中選擇一個操作進行下一次搜尋。

搜尋0,3,6時,即移動操作的時候,需要列舉他所能到達的所有地方。注意可以往前移動也可以往後移動。進行移動的人必須還沒有進行過移動,因為每種操作只能進行一次,也不能是被舉起或舉起別人的狀態。他所到達的地方必須是個空位置。

搜尋1,4,7時,即舉起別人的操作的時候,當前進行操作的人的狀態不能是正在被舉起,也不能是正在舉起別人或者是已經進行過舉起別人的操作。他能舉起的只有和他距離相差為1的人。如果他旁邊的人正在舉起其他人,則他可以把這兩個人一起舉起來,要注意把最上面的人的位置也要修改一下。如果他旁邊的人正在被別人舉起,則不能重複舉起這個人。

搜尋2,5,8時,即拋的操作時,當前進行操作的人頭頂必須有人,而且不能是正在被別人舉起的狀態。

剪枝:

首先,9個動作全排列,解答樹不會超過9層。可以用一個step記錄層數,用一個visit[]陣列來記錄每個操作是否執行過,下次深搜的時候只搜尋那些9個操作中沒有被執行過的操作。當前節點的搜尋結束後visit[]陣列要回溯。
第一次操作,只能是舉起旁邊的人或者移動,不可能是拋,因為頭頂還沒人。
最後一次操作,只能是往前走最遠的距離或者向前拋最大的距離。舉起操作不用考慮。

移動的時候,如果後面沒有人,則肯定沒有必要向後走。如果後面有人,也不需要從最大距離開始列舉,只需要從最後面的人的前面一個位置開始列舉就好了。當然移動的距離不能超過可以移動的最大距離。

拋也是如此,如果後面沒有人,則沒必要往後拋。也不需要從能往後拋的最大距離開始列舉,只需要從最後面的一個人的前一個位置開始列舉。拋的距離不能超過可以拋的最大距離。

我覺得移動的時候大多數位置是沒有意義的,需要列舉的位置僅僅是其他人的旁邊的位置或者能移動的最大距離這兩種。

拋也是這樣,只需要拋到其他人的旁邊,或者拋到能向前拋的最大的距離。

下面是程式碼,加了滿滿的註釋。throw的部分沒什麼註釋,是因為感覺和move,lift部分的註釋會有重複。
如果程式碼有問題請指正,歡迎討論。

#include <iostream>
#include <cmath>
#include <cstring>
#define MAXLEN 50
using namespace std;

struct People
{
    int pos;
    bool lifted;//正在被舉著
    bool lifting;//正在舉著別人
    int lift;//舉著的是誰
    int maxMove;//最大移動距離
    int maxThrow;//最大拋距離
    bool hasMoved;//是否移動過
    bool hasLifted;//是否舉過別人
    //沒有必要加上是否拋過別人的標記,因為只能舉起別人一次
}p[3];
//數軸,標記當前位置上是否有人
bool Pos[MAXLEN]; 
//3個人每人3種動作,一共9中操作。全排列的話需要標記一下,一個排列中不能有重複操作
bool visit[10];
int Max = 0;//記錄最大距離

/*
 * dfs 深搜查找可能的結果
 * @prarm k 表示當前是第n個人執行第m種操作。
 * 其中 n = k/3, m = k%3;
 * @prarm step 全排列中一共有9個元素,step指明當前操作排到了第幾個元素。
 * @return void
 */
void dfs(int k, int step)
{

    int n = k / 3; //當前執行操作的人
    int m = k % 3; //當前執行的動作
    // move
    if(!m) {
        //如果此人正在被別人舉著或者正在舉著別人,或者已經移動過了,那麼他/她不能移動。
        if(p[n].lifted || p[n].lifting || p[n].hasMoved) return;

        int i = 1;
        if(step == 9) i = p[n].maxMove;//如果當前是最後一步,那麼直接向前移動可以移動的最遠的距離
        //如果不是最後一步,那麼他也不必從他能移動的最靠後的距離開始搜尋
        //他只需要從 他的位置之前的 有人的位置 的前一個位置 開始搜尋即可
        //如果他後面沒人,那麼他走的距離只需要從1開始搜尋,不需要往後走,只需要往前走
        else {
            for(int j = 1; j < p[n].pos; j++) {
                if(Pos[j]) {
                    int l = -(p[n].pos - j -1);
                    i = l < i ? l : i;
                }
            }
            //走的距離不能超過maxMove
            i = i > -p[n].maxMove ? i : -p[n].maxMove;
        }
        for(; i <= p[n].maxMove; i++) {
            if(Pos[p[n].pos+i-1] || Pos[p[n].pos+i+1] || i == p[n].maxMove){
                if(p[n].pos + i > 0 && !Pos[p[n].pos + i]) {
                    if(!i) continue;

                    Pos[p[n].pos] = false;//當前位置置為false
                    p[n].pos += i;//向前走
                    Pos[p[n].pos] = true;//走到的新位置置為true
                    p[n].hasMoved = true;//標記一下,已經移動過了
                    Max = p[n].pos  > Max ? p[n].pos : Max;//記錄最大距離

                    //繼續搜尋
                    for(int j = 0; j < 9; j++) {
                        if(!visit[j]) {
                            visit[j] = true;
                            dfs(j, step+1);
                            visit[j] = false;//回溯
                        }
                    }
                    //回溯
                    p[n].hasMoved = false;
                    Pos[p[n].pos] = false;
                    p[n].pos -= i;
                    Pos[p[n].pos] = true;
                }
            }
        }
    } 
    // lift
    else if(m == 1) {
        //如果當前這個人真在被舉著或者真在舉著別人,或者已經舉起過別人了,那麼他/她將不能再舉起別人。
        if(p[n].lifted || p[n].lifting || p[n].hasLifted) return;
        for(int i = 0; i < 3; i++) {
            //如果旁邊有人
            if(abs(p[i].pos-p[n].pos) == 1) {
                //如果旁邊的這個人已經被別人舉起了,則不能重複舉起
                if(p[i].lifted) continue;

                p[n].hasLifted = true;
                p[n].lifting = true;
                p[n].lift = i;
                p[i].lifted = true;
                int temp = p[i].pos;
                Pos[p[i].pos] = false;
                p[i].pos = p[n].pos;
                //如果當前舉起的人真在舉著其他人,那麼這兩個人的位置必須同步改變
                if(p[i].lifting) {
                    int j = p[i].lift;
                    p[j].pos = p[i].pos;
                }

                //繼續搜尋
                for(int j = 0; j < 9; j++) {
                    if(!visit[j]) {
                        visit[j] = true;
                        dfs(j, step+1);
                        visit[j] = false;
                    }
                }
                //回溯
                p[n].hasLifted = false;
                p[n].lifting = false;
                p[n].lift = -1;
                p[i].lifted = false;
                p[i].pos = temp;
                Pos[p[i].pos] = true;
                if(p[i].lifting) {
                    int j = p[i].lift;
                    p[j].pos = p[i].pos;
                }
            }
        }
    }
    // throw
    else {
        //如果當前這個人正在被舉起,或者他/她並沒有舉起別人,那麼他/她不能執行拋的動作
        if(!p[n].lifting || p[n].lifted) return;

        int i = 1;
        if(step == 9) i = p[n].maxThrow;
        else {
            for(int j = 1; j < p[n].pos; j++) {
                if(Pos[j]) {
                    int l = -(p[n].pos - j -1);
                    i = l < i ? l : i;
                }
            }
            i = i > -p[n].maxThrow ? i : -p[n].maxThrow;
        }

        for(; i <= p[n].maxThrow; i++) {
            if(p[n].pos + i > 0 && !Pos[p[n].pos + i]) {
                if(Pos[p[n].pos+i-1] || Pos[p[n].pos+i+1] || i == p[n].maxThrow) {
                    int j = p[n].lift;
                    p[j].pos += i;
                    p[n].lifting  = false;
                    p[n].lift = -1;
                    p[j].lifted = false;
                    Pos[p[j].pos] = true;
                    Max = p[j].pos > Max ? p[j].pos : Max;
                    if(p[j].lifting) {
                        int k = p[j].lift;
                        p[k].pos = p[j].pos;
                    }
                    for(int q = 0; q < 9; q++) {
                        if(q == k) continue;
                        if(!visit[q]) {
                            visit[q] = true;
                            dfs(q, step+1);
                            visit[q] = false;
                        }
                    }
                    //回溯
                    Pos[p[j].pos] = false;
                    p[j].pos -= i;
                    p[j].lifted = true;
                    p[n].lift = j;
                    p[n].lifting = true;
                    if(p[j].lifting) {
                        int k = p[j].lift;
                        p[k].pos = p[j].pos;
                    }
                }
            }
        }
    }
}
int main()
{
    memset(Pos, false, sizeof(Pos));
    memset(visit, false, sizeof(visit));
    //輸入
    for(int i = 0; i < 3; i++) {
        cin >> p[i].pos >> p[i].maxMove >> p[i].maxThrow;
        p[i].lifted = p[i].lifting = p[i].hasMoved = p[i].hasLifted = false;
        p[i].lift = -1;
        Pos[p[i].pos] = true;
    }
    //深搜
    for(int i = 0; i < 9; i++) {
        //一個合法的第一步,不可能是拋。必須先移動或者舉起別人
        if((i % 3) != 2) 
        {
            visit[i] = true;
            dfs(i, 1);
            visit[i] = false;//回溯
        }
    }
    //結果
    cout << Max << endl;
    return 0;
}