OJ 7223 至少有多少隻惱人的大青蛙?__深搜

描述

有一種大青蛙會跳越稻田,從而踩踏稻子。農民在早上看到被踩踏的稻子,希望知道晚上有多少青蛙穿越自家的稻田。每隻青蛙總是沿着一條直線跳躍稻田,而且每次跳躍的距離都相同。
這裏寫圖片描述

如圖1和圖2所示,稻田裏的稻子組成一個柵格,每棵稻子位於一個格點上。而青蛙總是從稻田的一側跳進稻田,然後沿着某條直線穿越稻田,從另一側跳出去。
這裏寫圖片描述

如圖3所示,可能會有多隻青蛙從稻田穿越。青蛙的每一跳都恰好踩在一棵水稻上,將這棵水稻踩壞。有些水稻可能被多隻青蛙踩踏。當然,農民所見到的是圖4中的情形,並看不到圖3中的直線,也見不到別人家田裏被踩踏的水稻。需要注意的是,農民可以根據水稻踩壞的程度來精確計算出一共幾隻青蛙踩踏過這一棵水稻,所以聰明的農民就能得到圖4的水稻毀壞程度圖。
這裏寫圖片描述

我們可以認爲每條青蛙跳躍路徑上至少包括3棵被踩踏的水稻。而在一條青蛙跳躍路徑的直線上,也可能會有些被踩踏的水稻不屬於該行走路徑,例如青蛙跳躍路徑(2,3)(4,5)(6,7)經過(3,4),而(3,4)不屬於跳躍路徑。

注意:同一只青蛙不會第二次跳入同一片稻田,可能存在不同的兩隻青蛙的跳躍路徑完全一樣。

現在給定水稻破壞程度圖,請你寫一個程序,確定:最少有多少隻青蛙穿越了稻田。例如,圖4的答案是4,圖3所示的跳躍路徑數量就是答案。輸入數據保證有解且最多14只青蛙穿越稻田。

輸入

從標準輸入設備上讀入數據。第一行爲測試數據數目。每組測試數據第一行上兩個整數R、C,分別表示稻田中水稻的行數和列數,1≤R、C≤50。第二行是一個整數N,表示被踩踏的水稻數量, 3≤N≤700。在剩下的N行中,每行有三個整數,分別是一顆被踩踏水稻的行號(1~R)、列號(1~C)和被踩踏次數,三個整數之間用空格隔開。每棵被踩踏水稻只被列出

輸出

對於每一組測試數據從標準輸出設備上輸出一個整數,表示最少有幾隻青蛙穿越了稻田。

樣例輸入

1
6 7
14
2 1 1
2 2 1
2 3 2
2 4 1
2 5 1
2 6 2
2 7 1
3 4 1
4 2 1
4 5 1
6 1 1
6 3 1
6 5 1
6 7 2

樣例輸出

4

分析

最開始看的題就是惱人的青蛙,最後一道題沒想到會又與這個話題有關。枚舉前兩個點,之後再遞歸看能否讓所有的點歸於有效路徑裏面,按原來的思路改寫後TLE,之後又加了些剪枝方式,示例降到1ms,但OJ上測試還是TLE,嘗試多次無果,先放着吧,有空再想想,如果有了解怎麼有效剪枝麻煩回覆下這裏寫圖片描述

實現

#include <iostream>
#include <algorithm>
#include <vector>
#include <ctime>
#include <map>
#define KEY(plant) ((plant.x << 6) + plant.y)
using namespace std;
int r, c, n, totalPlantCount, minPathCount;

struct PLANT
{
    int x, y, num;
};

PLANT plants[701];
PLANT plant;
map<int, int> pointMap;

vector<int> searchPath(int i, int j, PLANT secPlant, int dX, int dY);

void findNextPathStartPlant(int startX, int plantCount, int pathCount) {
    //如果所有踐踏點都已覆蓋掉的話則更新最小路徑數,即最小青蛙數。
    if (plantCount == 0) {
        minPathCount = minPathCount > pathCount ? pathCount : minPathCount;
        return;
    }
    if (pathCount >= minPathCount) {
        return;
    }
    int dX, dY, pX, pY, findFirstJ;
    for (int i = startX; i < n - 2; i++) {
        if (plants[i].num <= 0) continue;//plants[i]是第一個點,必須保證未用完踩踏點。
        findFirstJ = 0;
        for (int j = i + 1; j < n - 1; j++) {
            if (plants[j].num <= 0) continue;//plants[j]是第二個點,必須保證未用完踩踏點。
            if (plants[i].num <= 0) break;
            if (!findFirstJ) {
                findFirstJ = j;
            }
            dX = plants[j].x - plants[i].x;
            dY = plants[j].y - plants[i].y;
            pX = plants[i].x - dX;
            pY = plants[i].y - dY;
            //第一點的前一點在稻田裏, 說明本次選的第二點導致的x方向步長不合理(太小)
            //取下一個點作爲第二點
            if (pX <= r && pX >= 1 && pY <= c && pY >= 1) continue;
            //x方向過早越界了. 說明本次選的第二點不成立
            //如果換下一個點作爲第二點, x方向步長只會更大, 更不成立, 所以應該
            //認爲本次選的第一點必然是不成立的, 那麼取下一個點作爲第一點再試
            pX = plants[j].x + dX;
            if (pX > r || pX < 1) break;
            pY = plants[j].y + dY;
            if (pY > c || pY < 1) continue; //y方向過早越界了, 應換一個點作爲第二點再試
            vector<int> plantPos = searchPath(i, j, plants[j], dX, dY);
            if (plantPos.size() > 0) {
                plantCount -= plantPos.size();
                //是一條路徑則將所有踩踏點的可踩踏數減1。
                for (std::vector<int>::const_iterator iter = plantPos.cbegin(); iter != plantPos.cend(); ++iter) {
//                  PLANT currPlant = plants[(*iter)];
                    //                  cout << "(" << currPlant.x << ", " << currPlant.y << ", " << currPlant.num << ") ";
                    plants[(*iter)].num--;
                }
                findNextPathStartPlant(i + 1, plantCount, pathCount + 1);
                //恢復
                plantCount += plantPos.size();
                for (std::vector<int>::const_iterator iter = plantPos.cbegin(); iter != plantPos.cend(); ++iter) {
                    plants[(*iter)].num++;
                }
            }
        }
        if (plants[i].num > 0) {
            //當前點沒消耗完說明是孤立點,永遠不可能踩到。
            return;
        }
        if (findFirstJ) { i = findFirstJ; }  //直到跳到下一個可用的點。
    }
}

int main() {
//  freopen("in.txt", "r", stdin);
//  const clock_t begin_time = std::clock();
    int t;
    scanf("%d", &t);
    while (t--) {
        totalPlantCount = 0;
        minPathCount = 15;
        pointMap.clear();
        scanf("%d %d", &r, &c);
        //行數和列數, x方向是上下, y方向是左右
        scanf("%d", &n);
        for (int i = 0; i < n; i++) {
            scanf("%d %d %d", &plants[i].x, &plants[i].y, &plants[i].num);
            pointMap[KEY(plants[i])] = i;
            totalPlantCount += plants[i].num;
        }
        //將水稻按x座標從小到大排序, x座標相同按y從小到大排序
        sort(plants, plants + n);
        findNextPathStartPlant(0, totalPlantCount, 0);
        printf("%d\n", minPathCount);
    }
//  std::cout << float(clock() - begin_time) << "ms" << endl;
}

bool operator <(const PLANT& p1, const PLANT& p2) {
    if (p1.x == p2.x) return p1.y < p2.y;
    return p1.x < p2.x;
}

bool operator ==(const PLANT& p1, const PLANT& p2) {
    return p1.x == p2.x && p1.y == p2.y;
}

int binarySearch(PLANT* a, int start, int end, PLANT key) {
    int l = start, r = end - 1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (a[mid] == key) return mid;
        else if (a[mid] < key) l = mid + 1;
        else r = mid - 1;
    }
    return -1;
}

//判斷從 secPlant點開始, 步長爲dx, dy, 那麼最多能走幾步
vector<int> searchPath(int i, int j, PLANT secPlant, int dX, int dY) {
    vector<int> plantPos;
    plantPos.push_back(i);
    plantPos.push_back(j);
    PLANT plant;
    plant.x = secPlant.x + dX;
    plant.y = secPlant.y + dY;
//  int pos = j, end;
    while (plant.x <= r && plant.x >= 1 && plant.y <= c && plant.y >= 1) {
//      end = pos + r * dX + dY + 1;
//      if (end >= n) {
//          end = n;
//      }
//      pos = binarySearch(plants, pos, end, plant);
        int key = KEY(plant);
        if (pointMap.count(key) > 0 && plants[pointMap[key]].num > 0) {
            plantPos.push_back(pointMap[key]);
            plant.x += dX;
            plant.y += dY;
        }
        else {
            //not found, 每一步都必須踩倒水稻纔算合理, 否則這就不是一條行走路徑
            plantPos.clear();
            break;
        }
    }
    return plantPos;
}
發佈了104 篇原創文章 · 獲贊 68 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章