算法競賽入門經典(第二版)-劉汝佳-第七章 暴力求解法 習題(11/18)

說明

本文是我對第七章18道習題的練習總結,建議配合紫書——《算法競賽入門經典(第2版)》閱讀本文。
另外爲了方便做題,我在VOJ上開了一個contest,歡迎一起在上面做:第七章習題contest
如果想直接看某道題,請點開目錄後點開相應的題目!!!

習題

習7-1 UVA 208 消防車

題意
輸入一個n(n≤20)個結點的無向圖以及某個節點k,按照字典序從小到大順序輸出從節點1到節點k的所有路徑,要求結點不能重複經過。
思路
這個題要事先判斷節點1是否可以到達節點k,否則會超時。有很多種方法可以判斷:比如DFS遍歷,或者用並查集等。
然後DFS遍歷即可,但考慮到算法效率,可以採取回溯+剪枝的方案(當然不剪枝也是可以AC的,時間長一點而已)。

我這個題卡在判斷節點1是否可以到達節點k這一步上很久。我的代碼主函數中第6行原來是:

n = 0;

後來查了很久,才發現改成

n = k;

就能AC。
按照我原來的算法邏輯,n=0的情況下,所給數據有可能出現這一種情況:

如果給出的所有路徑中出現的節點都小於k,這樣得到的n將小於k。

而這時候節點1肯定無法到達節點k,第一步的判斷應該可以給出正確答案。
但結果就是我不改的話會WA,改了就AC。我目前從算法邏輯上仍然沒有想明白。
如果有哪位大神知道,請不吝指點。

另外本題可以剪枝,可以在時間複雜度上有重大優化,請參考其他博客。

代碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 21;

int n, k;
vector<int> neigh[N];
int v[N];
int path_count;
vector<int> path;

bool dfs(int u)
{
    if (u == k) return true;

    for (int i = 0; i < neigh[u].size(); i++) {
        if (!v[neigh[u][i]]) {
            int x = neigh[u][i];
            v[x] = 1;
            if (dfs(x)) return true;
        }
    }
    return false;
}

void find_path()
{
    path_count++;
    for (int i = 0; i < path.size(); i++)
        printf("%d%c", path[i], i == path.size()-1 ? '\n' : ' ');
}

void search(int u)
{
    if (u == k) { find_path(); return; }

    for (int i = 0; i < neigh[u].size(); i++) {
        if (!v[neigh[u][i]]) {
            int x = neigh[u][i];
            v[x] = 1;
            path.push_back(x);
            search(x);
            path.resize(path.size()-1);
            v[x] = 0;
        }
    }
}

int main()
{
    int kase = 0;
    while (scanf("%d", &k) != EOF) {
        int a, b;
        int G[N][N];
        memset(G, 0, sizeof(G));
        n = k;
        while (scanf("%d%d", &a, &b), a || b) {
            n = max(n, max(a, b));
            G[a][b] = G[b][a] = 1;
        }
        for (int i = 1; i <= n; i++) {
            neigh[i].clear();
            for (int j = 1; j <= n; j++) {
                if (G[i][j]) neigh[i].push_back(j);
            }
        }

        printf("CASE %d:\n", ++kase);
        memset(v, 0, sizeof(v));
        v[1] = 1;
        path_count = 0;
        if (dfs(1)) {
            path.clear();
            memset(v, 0, sizeof(v));
            v[1] = 1;
            path.push_back(1);
            search(1);
        }
        printf("There are %d routes from the firestation to streetcorner %d.\n", path_count, k);
    }       
            
    return 0;
}

習7-2 UVA 225 黃金圖形

題意
平面上有k個障礙點。從(0,0)點出發,第一次走1個單位,第二次走2個單位,……,第n次走n個單位,恰好回到(0,0)。要求只能沿着東南西北方向走,且每次必須轉彎90°(不能沿着同一個方向繼續走,也不能後退)。走出的圖形可以自交,但不能經過障礙點。
思路
首先這個題目的翻譯是有問題的,漏掉了一個很重要的判斷條件:每一個落腳點不能與前一個相同(出發時的原點不算)。
然後我就在不知情的情況下各種WA。後來參考了其它博客才通過的。

另外這個題我覺得條件約束給的不好,應該說清楚每個城市的座標範圍,給一個基本約束,比如說座標範圍在-100到100之間。我後來是參考其它博客定義的座標範圍。
不過,沒有給座標範圍的話這個題也能做,用兩個set分別存儲故障點和落腳點,用於判重即可。我開始用了set,因爲題意不清的原因提交後WA給改了,就成了現在的代碼。
代碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;

const char tow[] = "ensw";
const int dir[4][2] = {{1, 0}, {0, 1}, {0, -1}, {-1, 0}};

typedef pair<int, int> P;

int n, k;
int G[250][250];
const int OFF = 105;
set<P> block;
int path_count;
vector<int> path;

void found_path()
{
    for (int i = 0; i < path.size(); i++)
      printf("%c", tow[path[i]]);
    printf("\n");
    path_count++;
}

void dfs(P p)
{
    if (path.size() == n) {
        if (p == P(0, 0))
          found_path();
        return;
    }

    int m = path.size();
    for (int i = 0; i < 4; i++) {
        if (m && (path[m-1]+1)%4/2 == (i+1)%4/2) continue;
        P p1 = p;
        bool flag = true;
        for (int j = 1; j <= m+1; j++) {
            p1.first += dir[i][0];
            p1.second += dir[i][1];
            int x = p1.first, y = p1.second;
            if (abs(x) > OFF || abs(y) > OFF || G[p1.first+OFF][p1.second+OFF] == -1) {flag = false; break;}
        }
        if (flag && G[p1.first+OFF][p1.second+OFF] != 1) {
            path.push_back(i);
            G[p1.first+OFF][p1.second+OFF] = 1;
            dfs(p1);
            G[p1.first+OFF][p1.second+OFF] = 0;
            path.resize(m);
        }
    }
}

int main()
{
    int kase;
    scanf("%d", &kase);
    for (int t = 1; t <= kase; t++) {
        scanf("%d%d", &n, &k);
        int x, y;
        memset(G, 0, sizeof(G));
        for (int i = 0; i < k; i++) {
            scanf("%d%d", &x, &y);
            G[x+OFF][y+OFF] = -1;
        }

        path_count = 0;
        path.clear();
        dfs(P(0, 0));
        printf("Found %d golygon(s).\n\n", path_count);
    }

    return 0;
}

習7-3 UVA 211 多米諾效應

題意
大概題意是有題目中的28種12的色塊,拼成了一個78的矩形,讓你算出有多少種色塊可以拼出這種矩形並輸出編號。

思路
直接dfs即可,對剪枝沒有什麼要求。

代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int INF = 0x3f3f3f3f;
const int MAXN = 5001;
const int MAXM = 51;

int n, m, ans;
int G[8][9], V[8][9], T[8][9]; //G爲原圖,V爲目標圖,T爲轉換規則表
int C[29]; //編號是否使用過
int d[2][2] = { { 0, 1 }, { 1, 0 } };

void print(int A[8][9]) {
	FOR1(i, 0, 6) {
		FOR1(j, 0, 7) { 
			printf("%4d", A[i][j]);
		}
		puts("");
	}
	puts("");
}

void DFS(int x, int y, int c) {
	if (c == 28) { ans++; print(V); return; }
	if (y == 8) x++, y = 0;
	if (V[x][y]) DFS(x, y + 1, c); //從左到右,從上到下遍歷
	else {
		for (int i = 0; i < 2; i++) {
			int p = x + d[i][0], q = y + d[i][1];
			if (p >= 7 || q >= 8 || V[p][q]) continue;
			int k = T[G[x][y]][G[p][q]]; //找到對應編號
			if (C[k]) continue;
			V[x][y] = V[p][q] = k; C[k] = 1;
			DFS(x, y + 1, c + 1);
			V[x][y] = V[p][q] = 0; C[k] = 0;
		}
	}
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int kase = 0;
	while (scanf("%d", &G[0][0]) == 1) {
		FOR1(i, 0, 6) FOR1(j, 0, 7) {
			if (i || j) scanf("%d", &G[i][j]);
		}
		memset(V, 0, sizeof(V));
		memset(C, 0, sizeof(C));
		int a = 1;
		FOR1(i, 0, 6) FOR1(j, i, 6) T[i][j] = T[j][i] = a++; //計算轉換表

		ans = 0;
		if (kase) cout << "\n\n\n";
		printf("Layout #%d:\n\n", ++kase);
		print(G);
		printf("Maps resulting from layout #%d are:\n\n", kase);
		DFS(0, 0, 0);
		printf("There are %d solution(s) for layout #%d.\n", ans, kase);
	}
	return 0;
}

習7-4 UVA 818 切斷圓環鏈(未嘗試)

題意

思路

代碼



習7-5 UVA 690 流水線調度(未嘗試)

題意

思路

代碼



習7-6 UVA 12113 重疊的正方形(未通過,WA)

題意
給定一個44的棋盤和棋盤上所呈現出來的紙張邊緣,如圖7-29所示,問用不超過6張22的紙能否擺出這樣的形狀。
思路
需要剪枝,不然會超時。主要是判斷邊緣情況,可以直接排除一些正方形,然後直接遍歷求解即可。我寫的是循環,實際上用DFS可讀性更好。
注意我這份代碼是WA的,但是跑了基礎測試用例以及udebug上的測試用例都能通過。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int INF = 0x3f3f3f3f;

char G[12][12];
bool legal[9];
int S[2][6][6], D[2][6][6];

void process_legal() {
	FOR1(k, 0, 8) {
		legal[k] = true;
		if (k == 4) continue; //最中間的無法確定
		int i = k / 3, j = k % 3;
		if (i == 0 && (!D[1][0][j] || !D[1][0][j + 1])) { legal[k] = false; continue; }
		if (i == 2 && (!D[1][4][j] || !D[1][4][j + 1])) { legal[k] = false; continue; }
		if (j == 0 && (!D[0][i][0] || !D[0][i + 1][0])) { legal[k] = false; continue; }
		if (j == 2 && (!D[0][i][4] || !D[0][i + 1][4])) { legal[k] = false; continue; }
	}
}

void put(int k, int A[2][6][6]) {
	int i = k / 3, j = k % 3;
	A[1][i][j] = A[1][i][j + 1] = A[1][i + 2][j] = A[1][i + 2][j + 1] = 1;
	A[1][i + 1][j] = A[1][i + 1][j + 1] = 0;
	A[0][i][j] = A[0][i + 1][j] = A[0][i][j + 2] = A[0][i + 1][j + 2] = 1;
	A[0][i][j + 1] = A[0][i + 1][j + 1] = 0;
}

bool issame(int A1[2][6][6], int A2[2][6][6]) {
	FOR1(i, 0, 1) {
		FOR1(j, 0, 4) {
			if (i == 0 && j == 4) continue;
			FOR1(k, 0, 4) {
				if (i == 1 && k == 4) continue;
				if (A1[i][j][k] != A2[i][j][k]) return false;
			}
		}
	}
	return true;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int T = 0;
	while (fgets(G[0], 20, stdin) && G[0][0] != '0') {
		FOR1(i, 0, 4) {
			if (i) fgets(G[i], 20, stdin);
			FOR1(j, 0, 8) {
				D[j & 1][i - ((j + 1) & 1)][j / 2] = (G[i][j] == ' ') ? 0 : 1;
			}
		}
		process_legal();
		int find = false;
		FOR1(i1, 0, 8) {
			memset(S, 0, sizeof(S));
			if (find) break;
			if (!legal[i1]) continue;
			put(i1, S);
			if (issame(S, D)) {
				find = true; break;
			}
			FOR1(i2, 0, 8) {
				if (find) break;
				if (!legal[i2] || i1 == i2) continue;
				put(i2, S);
				if (issame(S, D)) {
					find = true; break;
				}
				FOR1(i3, 0, 8) {
					if (find) break;
					if (!legal[i3] || i1 == i3 || i2 == i3) continue;
					put(i3, S);
					if (issame(S, D)) {
						find = true; break;
					}
					FOR1(i4, 0, 8) {
						if (find) break;
						if (!legal[i4] || i1 == i4 || i2 == i4 || i3 == i4) continue;
						put(i4, S);
						if (issame(S, D)) {
							find = true; break;
						}
						FOR1(i5, 0, 8) {
							if (find) break;
							if (!legal[i5] || i1 == i5 || i2 == i5 || i3 == i5 || i4 == i5) continue;
							put(i5, S);
							if (issame(S, D)) {
								find = true; break;
							}
							FOR1(i6, 0, 8) {
								if (find) break;
								if (!legal[i6] || i1 == i6 || i2 == i6 || i3 == i6 || i4 == i6 || i5 == i6) continue;
								put(i6, S);
								if (issame(S, D)) {
									find = true; break;
								}
							}
						}
					}
				}
			}
		}
		if (find) printf("Case %d: Yes\n", ++T);
		else printf("Case %d: No\n", ++T);
	}
	return 0;
}

習7-7 UVA 12558 埃及分數(未嘗試)

題意

思路

代碼



習7-8 UVA 12107 數字謎

題意
給出一個數字謎,要求修改儘量少的數,使修改後的數字謎只有唯一解。注意不能有前導零,輸出字典序最小的答案。
思路
這個題目需要兩次DFS,第一次是找出表達式,第二次是判斷表達式的解是否唯一。
第一次DFS可以做的剪枝不是特別明顯,我只加了尾數相乘得到的尾數確定這個剪枝。當然還可以做別的剪枝,代碼上要稍微麻煩些。
第二次DFS有明顯的的2個剪枝:一是只需要枚舉數字a和b,c就能算出來,然後驗證c的正確性即可;二是隻要發現多餘1個解,立即返回false。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <queue>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

char S0[3][5], S[3][5], S2[3][5];
int maxd, L[3];
int X[3];

int check2() {
	int a = atoi(S2[0]), b = atoi(S2[1]);
	int c = a * b;
	int T[5];
	FOR2(i, L[2] - 1, 0) {
		T[i] = c % 10;
		c /= 10;
		if (S[2][i] != '*' && S[2][i] != T[i] + 48)
			return 0;
	}
	if (c) return 0;
	if (T[0] == 0 && L[2] > 1) return 0;
	return 1;
}

int DFS2(int m, int n) {
	if (m == 2) //原來這裏是m==3,後來看別人代碼才知道這裏提前判斷可以大大降低複雜度
		return check2();
	if (S2[m][n] != '*'){
		if (n == L[m] - 1) return DFS2(m + 1, 0);
		return DFS2(m, n + 1);
	}
	int res = 0;
	FOR1(i, 0, 9) {
		if (L[m] > 1 && n == 0 && i == 0) continue; //前導0的情況不考慮
		S2[m][n] = i + 48;
		if (n == L[m] - 1) res += DFS2(m + 1, 0);
		else res += DFS2(m, n + 1);
		S2[m][n] = S[m][n];
		if (res > 1) break; //加剪枝
	}
	return res;
}

bool DFS(int m, int n, int d) { //當前搜索到第m個數的第n位,深度爲d
	if (m == 3 || d == maxd) {
		if (d != maxd) return false; //因爲更低深度的已經搜索過
		//由於TLE,以下加一些剪枝(似乎效果一般)
		int a = S[0][L[0] - 1] - 48, b = S[1][L[1] - 1] - 48, c = S[2][L[2] - 1] - 48;
		if (a != '*' - 48 && b != '*' - 48 && c != '*' - 48) { //加剪枝
			if (a * b % 10 != c) return false;
		}
		if (a != '*' - 48 && c != '*' - 48) { //加剪枝
			if (a % 2 == 0 && c % 2 == 1) return false;
		}
		if (b != '*' - 48 && c != '*' - 48) { //加剪枝
			if (b % 2 == 0 && c % 2 == 1) return false;
		}
		//以上是新加的剪枝
		memcpy(S2, S, sizeof(S));
		if (DFS2(0, 0) == 1) {
			printf("%s %s %s\n", S[0], S[1], S[2]);
			return true;
		}
		return false;
	}
	FOR1(i, 0, 10) {
		char c = (i == 0) ? '*' : i + 47;
		if (L[m] > 1 && n == 0 && c == '0') continue; //前導0的情況不考慮
		int d1 = d + (S0[m][n] != c);
		//if (d1 > maxd) continue; //剪枝
		S[m][n] = c;
		if (n == L[m] - 1 && DFS(m + 1, 0, d1)) return true;
		if (n != L[m] - 1 && DFS(m, n + 1, d1)) return true;
		S[m][n] = S0[m][n];
	}
	return false;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int cas = 0;
	while (scanf("%s%s%s", S0[0], S0[1], S0[2]) == 3) {
		FOR1(i, 0, 2) L[i] = strlen(S0[i]);
		printf("Case %d: ", ++cas);
		FOR1(i, 0, 10) {
			maxd = i;
			memcpy(S, S0, sizeof(S0));
			if (DFS(0, 0, 0)) break;
		}
		memset(S0, 0, sizeof(S0));
	}
	return 0;
}

習7-9 UVA 1604 立體八數碼問題

題意
有8個立方體,按照相同方式着色(如圖7-31(a)所示,相對的面總是着相同顏色),然後以相同的朝向擺成一個3*3的方陣,空出一個位置(如圖7-31(b)所示,空位由輸入決定)。
每次可以把一個立方體“滾動”一格進入空位,使它原來的位置成爲空位,如圖7-32所示。
你的任務是用最少的移動使得上表面呈現出指定的圖案。輸入空位的座標和目標狀態中上表面各個位置的顏色,輸出最小移動步數。
思路
這個題主要有3點需要注意:
1、整體思路,顯然可以用BFS,但是直接用BFS會超時。我這裏用了雙向BFS,實際上也可以用BFS+優先隊列等其他方法。
2、可以控制兩個方向的搜索深度,時間效率可進一步優化。本題的雙向搜索深度分別爲20和10,時間效率還是比較優的。
3、編碼方式,每個立方體有6種狀態,加上空位狀態總共有7種,所以整個狀態是7^9,可以用編解碼錶示,詳見代碼。

1、還可以用BFS+優先隊列,不僅效率更高,寫法也更簡單。
2、理論上應該某個方向的某一深度全部搜索完畢之後,再搜索另一方向,否則可能出現錯誤。但我看有的程序並不是這麼做的,保留疑問。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <queue>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int INF = 0x3f3f3f3f;
const int MAXS = 40353607 + 10; //總共有7^9狀態數

int x, y;
int T[9];
queue<int> Q[2];
int ST[2][MAXS]; //對每個狀態,0表示上藍前白,1爲上藍前紅,2爲上白前藍,3爲上白前紅,4爲上紅前藍,5爲上紅前白,6爲空位
int d[4][2] = { { -1, 0 }, { 1, 0 }, { 0, 1 }, { 0, -1 } };
int RT[2][6] = { { 2, 4, 0, 5, 1, 3 }, { 5, 3, 4, 1, 2, 0 } }; //事先計算好旋轉的狀態,與d相對應

int BWR(char c) {
	if (c == 'B') return 0;
	if (c == 'W') return 1;
	if (c == 'R') return 2;
	return 3;
}

int encode(vector<int> vt) {
	int res = 0;
	FOR1(i, 0, 8)
		res = res * 7 + vt[i];
	return res;
}

vector<int> decode(int mm) {
	vector<int> vt;
	FOR1(i, 0, 8) vt.push_back(0);
	FOR2(i, 8, 0) {
		vt[i] = mm % 7;
		mm /= 7;
	}
	return vt;
}

void DFS_PUSH(int k, int s) {
	if (k == 9) {
		Q[1].push(s); ST[1][s] = 1;
		return;
	}
	FOR1(i, T[k] * 2, min(6, T[k] * 2 + 1))
		DFS_PUSH(k + 1, s * 7 + i);
}

int BFS() {
	memset(ST, 0, sizeof(ST));
	vector<int> vt, vt0;
	FOR1(i, 0, 8) vt.push_back(3);
	vt[y * 3 + x] = 6; //注意x和y是反過來的
	int s = encode(vt);
	Q[0].push(s); ST[0][s] = 1; //初始狀態放入正向隊列
	DFS_PUSH(0, 0); //把所有可能目標狀態放入反向隊列
	int depth[2]; depth[0] = depth[1] = 1;
	while (!Q[0].empty() && !Q[1].empty()) { //把雙向BFS放在一段代碼裏面容易出細節錯誤
		int r = (depth[0] > 21) ? 1 : 0;
		s = Q[r].front();
		depth[r] = max(depth[r], ST[r][s]);
		if (depth[r] + depth[1 - r] - 1 > 30) return -1;
		Q[r].pop();
		vt0 = decode(s);
		if (ST[1 - r][s]) return ST[r][s] + ST[1 - r][s] - 2;
		FOR1(i, 0, 8) {
			if (vt0[i] != 6) continue;
			int a0 = i / 3, b0 = i % 3;
			FOR1(j, 0, 3) {
				int a = a0 - d[j][0], b = b0 - d[j][1];
				if (a >= 0 && a <= 2 && b >= 0 && b <= 2) {
					vt = vt0;
					int ni = a * 3 + b;
					vt[i] = RT[j / 2][vt[ni]];
					vt[ni] = 6;
					int s1 = encode(vt);
					if (!ST[r][s1]) {
						Q[r].push(s1);
						ST[r][s1] = ST[r][s] + 1;
						if (ST[1 - r][s1]) return ST[r][s1] + ST[1 - r][s1] - 2;
					}
				}
			}
		}
	}
	return -1;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (cin >> x >> y && x) {
		x--, y--;
		char c[10];
		FOR1(i, 0, 8) {
			scanf("%s", c);
			T[i] = BWR(c[0]);
		}
		printf("%d\n", BFS());
		FOR1(r, 0, 1) while (!Q[r].empty()) Q[r].pop();
	}
	return 0;
}

習7-10 UVA 11214 守衛棋盤

題意
輸入一個n*m棋盤(n,m<10),某些格子有標記。用最少的皇后守衛(即佔據或者攻擊)所有帶標記的格子。
思路
類似於經典N皇后問題,這個題是迭代加深搜索。這篇博客說的比較清楚:
https://blog.csdn.net/qq_40772692/article/details/80914092
我在做的過程中:
1、之前犯了一個審題理解錯誤:皇后保護的不僅有斜線方向,還有行和列方向,我以爲只有斜線方向。
2、有兩種記錄狀態的方式,我這裏的方式跟網上主流的不太一樣,各有優缺點。我的方式需要保存整個數組,不過好處是遍歷的狀態少,網上主流的則相反。
3、寫的時候還是犯了很多細節錯誤,逐漸調試得到最終正確結果。這說明我的功力還是差很多。
4、如果沒加這個前4次搜索沒成功則直接輸出5的剪枝,可能這個題目還是會TLE。這說明我的方法複雜度可能還是要大於網上主流的方法。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <queue>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

int n, m, maxd;
char S[11][11];
int T, V[4][20]; //兩個方向上數組包含的棋子數量,本來想用位運算,後來發現不行(其實也行,只不過搜索的時候要全搜)
int tow[8][2] = { { 1, -1 }, { -1, 1 }, { 1, 1 }, { -1, -1 }, { 0, -1 }, { -1, 0 }, { 1, 0 }, { 0, 1 } };

bool DFS(int x, int y, int d) { //當前搜索到x,y位置,深度爲d
	if (x == n || d == maxd) {
		return !T; //T表示總的計數
	}
	int a = x + y, b = y + n - 1 - x;
	while (x < n && V[0][a] == 0 && V[1][b] == 0  && V[2][x] == 0 && V[3][y] == 0) {
		y++;
		if (y == n)  x++, y = 0;
		a = x + y, b = y + n - 1 - x;
	}
	if (x == n)
		return !T;
	int nx = x, ny = y + 1;
	if (ny == n)  nx++, ny = 0;
	if (DFS(nx, ny, d)) return true; //這是不在當前位置放棋子的情況
	
	char S2[11][11]; //這三個備份值應定義爲局部變量
	int T2, V2[4][20];
	memcpy(V2, V, sizeof(V)); //保存
	memcpy(S2, S, sizeof(S)); //保存
	T2 = T;
	if (S[x][y] == 'X')	{ //注意當前位置可能有也可能沒有棋子
		V[0][a]--, V[1][b]--, V[2][x]--, V[3][y]--, T -= 4;
		S[x][y] = '.';
	}
	FOR1(i, 0, 7) {
		int x1 = x + tow[i][0], y1 = y + tow[i][1];
		while (0 <= x1 && x1 <= n - 1 && 0 <= y1 && y1 <= m - 1) {
			if (S[x1][y1] == 'X') {
				V[0][x1 + y1]--, V[1][y1 + n - 1 - x1]--, V[2][x1]--, V[3][y1]--, T -= 4;
				S[x1][y1] = '.';
			}
			x1 += tow[i][0], y1 += tow[i][1];
		}
	}
	if (DFS(nx, ny, d + 1)) return true;
	memcpy(V, V2, sizeof(V2)); //還原
	memcpy(S, S2, sizeof(S2)); //還原
	T = T2;
	return false;
}


int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int cas = 0;
	while (cin >> n && n) {
		cin >> m;
		T = 0;
		memset(V, 0, sizeof(V));
		FOR1(i, 0, n - 1) {
			scanf("%s", S[i]);
			FOR1(j, 0, m - 1) {
				if (S[i][j] == 'X') {
					V[0][i + j]++;
					V[1][j + n - 1 - i]++;
					V[2][i]++;
					V[3][j]++;
					T += 4;
				}
			}
		}

		printf("Case %d: ", ++cas);
		FOR1(i, 0, 5) {
			if (i == 5) {
				printf("5\n"); //剪枝1
				break;
			}
			maxd = i;
			if (DFS(0, 0, 0)) {
				printf("%d\n", maxd);
				break;
			}
		}
	}
	return 0;
}

習7-11 UVA 12569 樹上的機器人規劃-簡單版

題意
有一棵n(4≤n≤15)個結點的樹,其中一個結點有一個機器人,還有一些結點有石頭。每步可以把一個機器人或者石頭移到一個相鄰結點。任何情況下一個結點裏不能有兩個東西(石頭或者機器人)。輸入每個石頭的位置和機器人的起點和終點,求最小步數的方案。如果有多解,可以輸出任意解。如圖7-33所示,s=1,t=5時,最少需要16步:機器人1-6,石頭2-1-7,機器人6-1-2-8,石頭3-2-1-6,石頭4-3-2-1,最後機器人8-2-3-4-5。
思路
這個題的重點是狀態壓縮,我的做法是記錄機器人的位置以及每個位置上是否空的,來作爲狀態。詳見代碼。
主體框架是BFS,沒有太複雜的東西。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <queue>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int INF = 0x3f3f3f3f;
const int MAXN = 15;

struct P {
	int x;
	int y;
	vector<int> vt;
	P(){}
	P(int x1, int y1, vector<int> vt1) {
		x = x1, y = y1, vt = vt1;
	}
};

int ks;
int n, m, s, t;
int OB[MAXN + 1], G[MAXN + 1][MAXN + 1];
int ST[MAXN][1 << MAXN]; //第一個維度表示機器人所在位置,第二個維度表示是否有障礙物(有就是1)

bool BFS() {
	memset(ST, 0, sizeof(ST)); //0表示有小球,1表示空洞
	int x = s, y = 0;
	FOR1(i, 0, m - 1) y |= (1 << OB[i]);
	y |= (1 << s);
	queue<P> Q;
	vector<int> vt0, vt;
	Q.push(P(x, y, vt));
	ST[x][y] = 1;
	while (!Q.empty()) {
		P p = Q.front(); Q.pop();
		x = p.x;
		y = p.y;
		vt0 = vt = p.vt;
		if (x == t) {
			printf("Case %d: %d\n", ks, ST[x][y]-1);
			int m = vt.size();
			FOR1(i, 0, m - 1) {
				printf("%d %d\n", vt[i] + 1, vt[i + 1] + 1);
				i++;
			}
			return true;
		}
		FOR1(i, 0, n - 1) {
			FOR1(j, 0, n - 1) {
				if (G[i][j] && (y&(1<<i)) && !((y>>j)&1)) { //i位置爲1,j位置爲0
					int x1 = x, y1 = y;
					if (i == x) x1 = j;
					y1 ^= (1 << i), y1 ^= (1 << j); //i和j位置取反
					if (!ST[x1][y1]) {
						vt = vt0;
						vt.push_back(i), vt.push_back(j);
						Q.push(P(x1, y1, vt));
						ST[x1][y1] = ST[x][y] + 1;
					}
				}
			}
		}
	}
	return false;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int kase;
	cin >> kase;
	for (ks = 1; ks <= kase; ks++) {
		cin >> n >> m >> s >> t;
		s--, t--;
		FOR1(i, 0, m - 1) {
			cin >> OB[i];
			OB[i]--;
		}
		int a, b;
		memset(G, 0, sizeof(G));
		FOR1(i, 1, n - 1) {
			cin >> a >> b;
			G[a-1][b-1] = G[b-1][a-1] = 1;
		}

		if (!BFS())
			printf("Case %d: -1\n", ks);
		printf("\n");
	}
	return 0;
}

習7-12 UVA 1533 移動小球

題意
如圖7-34所示,一共有15個洞,其中一個空着,剩下的洞裏各有一個小球。每次可以讓一個小球越過同一條直線上的一個或多個連續的小球,落到最近的空洞(不能越過空洞),然後拿走被跳過的小球。例如,讓14跳到空洞5中,則洞9裏的小球會被拿走,因此操作之後洞9和14會變空,而5裏面會有一個小球。你的任務是用最少的步數讓整個棋盤只剩下一個小球,並且位於初始時的那個空洞中。
輸入僅包含一個整數,即空洞編號,輸出最短序列的長度m,然後是m個整數對,分別表示每次跳躍的小球所在的洞編號以及目標洞的編號。
思路
典型的狀態壓縮DP+BFS。
主要是狀態壓縮,我的做法是用二級制記錄15個洞是否有小球的狀態。然後用BFS狀態轉移即可。
其實整體框架BFS跟上一個題目是一樣的。
需要注意的兩個地方:
1、小技巧:二進制數可以用異或操作來轉換狀態。
2、注意輸出要按照字典序最小原則。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <queue>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

typedef pair<int, vector<int> > P;

const int INF = 0x3f3f3f3f;
const int MAXN = 65536;

int n;
int T[6][6];
int ST[1 << 15];
int d[6][2] = { { -1, -1 }, { -1, 0 }, { 0, -1 }, { 0, 1 }, { 1, 0 }, { 1, 1 } }; //注意要按照字典序最小來輸出,因而這裏的順序就有講究

void pre_process() {
	int cnt = 0;
	FOR1(i, 0, 4) {
		FOR1(j, 0, i) {
			T[i][j] = cnt++;
		}
	}
}

bool legal(int x, int y) {
	return 0 <= x && x <= 4 && 0 <= y && y <= x;
}

bool BFS(int x) {
	x--;
	int G0[6][6], G[6][6]; //用於轉換
	memset(ST, 0, sizeof(ST)); //0表示有小球,1表示空洞
	queue<P> Q;
	vector<int> vt0, vt;
	Q.push(P(1 << x, vt));
	ST[1 << x] = 1;
	while (!Q.empty()) {
		P p = Q.front(); Q.pop();
		int a = p.first;
		vt0 = vt = p.second;
		if (a == (1 << 15) - 1 - (1 << x)) {
			printf("%d\n", ST[a] - 1);
			int m = vt.size();
			FOR1(i, 0, m - 2)
				printf("%d ", vt[i] + 1);
			printf("%d\n", vt[m-1] + 1);
			return true;
		}
		int cnt = 0;
		FOR1(i, 0, 4) {
			FOR1(j, 0, i) {
				G0[i][j] = G[i][j] = (a >> (cnt++)) & 1;
			}
		}
		int ni, nj;
		FOR1(i, 0, 4) {
			FOR1(j, 0, i) {
				if (!G0[i][j]) {
					FOR1(k, 0, 5) {
						memcpy(G, G0, sizeof(G));
						ni = i, nj = j;
						int cnt = 0;
						do {
							ni = ni + d[k][0], nj = nj + d[k][1];
							cnt++;
						} while (legal(ni, nj) && !G[ni][nj]);
						if (cnt >= 2 && legal(ni, nj)) { //找到可跳方案
							int b = a;
							ni = i, nj = j;
							FOR1(r, 0, cnt - 1) {
								b |= (1 << T[ni][nj]);
								ni = ni + d[k][0], nj = nj + d[k][1];
							};
							b &= ~(1 << T[ni][nj]);
							if (!ST[b]) {
								vt = vt0;
								vt.push_back(T[i][j]), vt.push_back(T[ni][nj]);
								Q.push(P(b, vt));
								ST[b] = ST[a] + 1;
							}
						}
					}
				}
			}
		}
	}
	return false;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	pre_process();
	int kase;
	cin >> kase;
	FOR1(ks, 1, kase) {
		int x;
		cin >> x;
		if (!BFS(x))
			printf("IMPOSSIBLE\n");
	}
	return 0;
}

習7-13 UVA 817 數字表達式*(未通過,RE)

題意
輸入一個以等號結尾、前面只包含數字的表達式,插入一些加號、減號和乘號,使得運算結果等於2000。表達式裏的整數不能有前導零(例如,0100或者000都是非法的),運算符都是二元的(例如,2*-100*-10+0=是非法的),並且符合通常的運算優先級法則。
輸入數字個數不超過9。如果有多解,按照字典序從小到大輸出;如果無解,輸出IMPOSSIBLE。例如,2100100=有3組解,按照字典序依次爲210010+0=、210010-0=和2100-100=。
思路
基本的DFS,不過有很多細節需要注意。比如前導零的判斷。
我的代碼已經通過了所給的測試用例以及udebug上的測試用例,但是提交後RE了,正在查找原因。
另外特別需要注意:
1、表達式2000=的輸出是IMPOSSIBLE。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int INF = 0x3f3f3f3f;
const int MAXN = 65536;

int n;
string S;
vector<string> res;

int comb(int a, char sig, int b) {
	if (sig == '+') return a + b;
	if (sig == '-') return a - b;
	return a * b;
}

bool check(string T) {
	int c = T.size();
	int a = 0, b = 0;
	char sig = '+';
	int i = 0;
	//cout << T << endl;
	while (i < c) {
		if (i == 0 || T[i] == '+' || T[i] == '-') {
			if (i) {
				a = comb(a, sig, b);
				sig = T[i++];
			}
			b = 0;
			while (T[i] >= '0' && T[i] <= '9') {
				b = b * 10 + (T[i] - 48);
				i++;
			}
		}
		else {
			i++;
			int b1 = 0;
			while (T[i] >= '0' && T[i] <= '9') {
				b1 = b1 * 10 + (T[i] - 48);
				i++;
			}
			b = comb(b, '*', b1);
		}
	}
	if (comb(a, sig, b) == 2000) {
		res.push_back(T);
		return true;
	}
	return false;
}

bool DFS(int k, string T) {
	if (k == n - 1) return check(T);
	int t = T.size();
	if (t == 1) {
		if (T[0] != '0') DFS(k + 1, T + S[k]);
	}
	else {
		if (!(T[t - 1] == '0' && (T[t - 2] < '0' || T[t - 2] > '9')))
			DFS(k + 1, T + S[k]);
	}
	DFS(k + 1, T + '+' + S[k]);
	DFS(k + 1, T + '-' + S[k]);
	DFS(k + 1, T + '*' + S[k]);
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int kase = 0;
	while (cin >> S && S[0] != '=') {
		n = S.size();
		res.clear();
		DFS(1, S.substr(0, 1));
		sort(res.begin(), res.end());
		printf("Problem %d\n", ++kase);
		if (res.size()) {
			FOR1(i, 0, res.size() - 1)
				cout << "  " << res[i] << '=' << endl;
		}
		else printf("  IMPOSSIBLE\n");
	}
	return 0;
}

習7-14 UVA 307 小木棍

題意
喬治有一些同樣長的小木棍,他把這些木棍隨意地砍成幾段,直到每段的長度都不超過50。現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己最開始時有多少根木棍和它們的分別長度。給出每段小木棍的長度,編程幫他找出原始木棍的最小可能長度。例如,若砍完後有4根,長度分別爲1, 2, 3, 4,則原來可能是2根長度爲5的木棍,也可能是1根長度爲10的木棍,其中5是最小可能長度。另一個例子是:砍之後的木棍有9根,長度分別爲5, 2, 1, 5, 2, 1, 5, 2, 1,則最小可能長度爲6(5+1=5+1=5+1=2+2+2=6),而不是8(5+2+1=8)。
思路
很容易想到根據木棍長度枚舉,然後用DFS找答案。關鍵是怎麼判斷。
首先要把木棍長度按照從大到小排序,然後按需搜索。
我參考了別人的博客,有這樣兩個重要的剪枝:
1、最長的木棍肯定要用到,如果用不到,這說明此方案失敗,返回。
2、相同長度的棍子,如果某一個沒用到,則下一個相同長度的也用不到。這一點在我代碼裏沒寫,實際可以加。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

const int INF = 0x3f3f3f3f;
const int MAXN = 65536;
int n;
int A[MAXN], V[MAXN], tot;

bool DFS(int s, int left, int L, int cnt) {
	if (!cnt) return true;
	FOR1(i, s, n - 1) {
		if (!V[i] && A[i] < left) {
			V[i] = 1;
			if (DFS(i + 1, left - A[i], L, cnt)) return true;
			V[i] = 0;
			if (left == L) return false; //說明第一個棒沒用到,這就沒必要繼續搜了
		}
		if (!V[i] && A[i] == left) {
			V[i] = 1;
			if (DFS(0, L, L, cnt - 1)) return true;
			V[i] = 0;
			return false; //說明剩下的棒沒成功,直接失敗
		}
	}
	return false;
}

int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	while (cin >> n && n) {
		tot = 0;
		FOR1(i, 0, n - 1) {
			cin >> A[i];
			tot += A[i];
		}
		sort(A, A + n, greater<int>());

		FOR1(L, A[0], tot) {
			if (tot % L) continue;
			memset(V, 0, sizeof(V));
			if (DFS(0, L, L, tot / L)) {
				printf("%d\n", L); break;
			}
		}
	}
	return 0;
}

習7-15 UVA 11882 最大的數

題意
在一個R行C列(2≤R,C≤15,R*C≤30)的矩陣裏有障礙物和數字格(包含1~9的數字)。你可以從任意一個數字格出發,每次沿着上下左右之一的方向走一格,但不能走到障礙格中,也不能重複經過一個數字格,然後把沿途經過的所有數字連起來,如圖7-35所示。
如圖7-35可以得到9784、4832145等整數。問:能得到的最大整數是多少?
思路
這是別人的AC代碼。有兩大亮點:
1、假設已經找到的答案數組是b,目前嘗試的數組是c,當前要填的位置是cur。答案的長度是maxd,如果b,c兩數組在cur之前的所有數字均相等,但當前要填的值val<b[cur],那麼可想而知,c的結果一定小於b,此時再往下尋找結果也不會更優,直接剪枝!
2、另外,通過maxd和當前位置cur可以知道還需要尋找的數字個數是res。如果當前填入的值val對應的座標是(x,y),用一個find(x,y)函數表示它後面最多還能找到的數字個數。可想而知,當find(x,y)<res時,即往下能夠找到的最多的數字個數還達不到最低要求時,需要剪枝。而find函數還可以通過遞歸來實現,詳細細節見代碼。

原文鏈接:https://blog.csdn.net/u014800748/article/details/45128759

由於我在思想上都理解了,所以暫時先不自己寫代碼了。
代碼



習7-16 UVA 11846 找座位(未通過,TLE)

題意
有一個n*n(n<20)的座位矩陣裏坐着k(k≤26)個研究小組。每個小組的座位都是矩形形狀。輸入每個小組組長的位置和該組的成員個數,找到一種可能的座位方案。如圖7-36所示是一組輸入和對應的輸出。
思路
顯然是DFS。但是我根據小組來找,超時了,後來又加了一些優化,應該是降低了2個數量級,還是超時。可能還需要降低1個數量級才行,但我目前的代碼不太好優化了。
小夥伴可以參考這篇博客:https://blog.csdn.net/qq_36973725/article/details/86185025

代碼



習7-17 UVA 11694 Gokigen Naname 謎題

題意
在一個n*n(n≤7)網格中,有些交叉點上有數字。你的任務是給每個格子畫一條斜線(一共只有“\”和“/”兩種),使得每個交叉點的數字等於和它相連的斜線條數,且這些斜線不會構成環,如圖7-37所示。
思路
稍微複雜一些的DFS,我的做法是從上到下從左到右逐個搜索,並在每一個行放置完成時判斷是否形成環。
判斷環的經典做法是並查集。
代碼中需要特別留意的是數組的恢復,其中我在並查集恢復中犯了細節錯誤,調試了很長時間才成功。
代碼

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <queue>
#include <vector>
using namespace std;

#define FOR1(i, a, b) for (int i = (a); i <= (int)(b); i++)
#define FOR2(i, a, b) for (int i = (a); i >= (int)(b); i--)

int n; //長方形的邊長
int C[10][10], nowC[10][10]; //表示斜線條數
char S[10][10]; //放置斜線的字符數組
int pre[100]; //並查集,用於判圈

bool legal(int a, int b) {
	if (a != -1 && b != a) return false;
	return true;
}

bool combine(int a, int b) {
	int ka = 0, kb = 0;
	while (a != pre[a]) {
		a = pre[a];
		ka++;
	}
	while (b != pre[b]) {
		b = pre[b];
		kb++;
	}
	if (a == b) return false;
	if (ka > kb) pre[b] = a;
	else pre[a] = b;
	return true;
}

bool DFS(int x, int y) { //當前搜索到x,y位置
	if (x && y == 0) { //說明某一行已經賦值完畢,判斷環,需要用並查集(注意要保存前n行的賦值,以便回溯)。一開始這個地方忽略了一種特殊情況,提交後WA了。
		if (x == 2)
			x = 2;
		if (x == 3)
			x = 3;
		int n1 = n + 1;
		FOR1(j, 0, n) pre[x*n1 + j] = x*n1 + j; //初始化新行
		FOR1(j, 0, n - 1) {
			if (S[x - 1][j] == '/') {
				if (!combine(x*n1 + j, (x - 1)*n1 + j + 1)) //說明有環
					return false;
			}
			else {
				if (!combine(x*n1 + j + 1, (x - 1)*n1 + j)) //說明有環
					return false;
			}
		}
	}
	if (x == n) { //說明已經搜索完畢
		FOR1(i, 0, n) { //判斷最後一行的斜線條數是否正確
			if (!legal(C[n][i], nowC[n][i])) return false;
		}
		FOR1(i, 0, n - 1) printf("%s\n", S[i]);
		return true;
	}
	int nx = x, ny = y + 1;
	if (ny == n)  nx++, ny = 0;
	int pre1[100]; //注意這裏的pre1一定要設爲局部變量,全局變量會出毛病
	if (legal(C[x][y], nowC[x][y])) {
		if (!(ny == 0 && !legal(C[x][n], nowC[x][n] + 1))) {
			S[x][y] = '/';
			nowC[x][y + 1]++, nowC[x + 1][y]++;
			if (ny == 0) memcpy(pre1, pre, sizeof(pre)); //備份並查集
			if (DFS(nx, ny)) return true;
			nowC[x][y + 1]--, nowC[x + 1][y]--; //還原計數
			if (ny == 0) memcpy(pre, pre1, sizeof(pre)); //還原並查集
		}
	}
	if (legal(C[x][y], nowC[x][y] + 1)) {
		if (!(ny == 0 && !legal(C[x][n], nowC[x][n]))) {
			S[x][y] = '\\';
			nowC[x][y]++, nowC[x + 1][y + 1]++;
			if (ny == 0) memcpy(pre1, pre, sizeof(pre)); //備份並查集
			if (DFS(nx, ny)) return true;
			nowC[x][y]--, nowC[x + 1][y + 1]--; //還原
			if (ny == 0) memcpy(pre, pre1, sizeof(pre)); //還原並查集
		}
	}
	return false;
}


int main() {
#ifdef CODE_LIANG
	freopen("datain.txt", "r", stdin);
	freopen("dataout.txt", "w", stdout);
#endif
	int kase;
	cin >> kase;
	FOR1(i, 1, kase) {
		scanf("%d", &n);
		char C1[10][10];
		FOR1(i, 0, n) {
			scanf("%s", C1[i]);
			FOR1(j, 0, n) {
				if (C1[i][j] == '.') C[i][j] = -1;
				else C[i][j] = C1[i][j] - 48;
			}
		}
		FOR1(i, 0, n - 1) S[i][n] = '\0';
		memset(nowC, 0, sizeof(nowC));
		FOR1(j, 0, n) pre[j] = j; //初始化第0行的並查集
		DFS(0, 0);
	}
	return 0;
}

習7-18 UVA 10384 推門遊戲(未嘗試)

題意
如圖7-38所示,從S處出發,每次可以往東、南、西、北4個方向之一前進。如果前方有牆壁,遊戲者可以把牆壁往前推一格。如果有兩堵或者多堵連續的牆,則不能推動。另外,遊戲者也不能推動遊戲區域邊界上的牆。
用最少的步數走出迷宮(邊界處沒有牆的地方就是出口)。迷宮總是有4行6列,多解時任意輸出一個移動序列即可(用NEWS這4字符表示移動方向)
思路
需要用IDA*算法。
代碼



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章