1. 程式人生 > >poj 1085 Triangle War 1568 Find the Winning Move 極大極小搜尋 alpha-beta剪枝

poj 1085 Triangle War 1568 Find the Winning Move 極大極小搜尋 alpha-beta剪枝

一,極大極小搜尋及alpha-beta剪枝(參考這裡)
在博弈搜尋中,比如:圍棋,五子棋,象棋等,結果有三種可能:勝利,失敗和平局。

理論上可以窮舉所有的走法,這就需要生成整棵博弈樹。實際上不可行。因此搜尋時可以限制博弈樹的深度,到達該深度則不再往下搜,相當於只往前看n步。

假設:max和min對弈,輪到max走棋了,那麼我們遍歷max的每一個可能走棋方法,然後對於前面max的每一個走棋方法,遍歷min的每一個走棋方法,然後接著遍歷max的每一個走棋方法,。。。直到分出了勝負或者達到了搜尋深度的限制。若達到搜尋深度限制時尚未分出勝負,則根據當前局面的形式,給出一個得分,計算得分的方法被稱為估價函式,不同遊戲的估價函式如何設計和具體的遊戲相關。

在搜尋樹中,輪到max走棋的節點為極大結點,輪到min走棋的節點是極小節點。

極大極小搜尋
確定估價函式,來計算每個棋局結點的估價值,對max方有利,估價值為正,對max越有利,估價值越大;對min方有利,估價值為負,對min方越有利,估價值越小。

從當前棋局的結點要決定下一步如何走時,以當前棋局節點為根,生成一棵深度為n的搜尋樹。不妨總是假設當前棋局結點是max結點。

用局面估價函式計算出每個葉子結點的估價值。

若某個非葉子結點是極大結點,則其估價值為其子節點中估價最大的那個結點的估價值。
若某個非葉子結點是極小結點,則其估價值為其子結點中估價最小的那個結點的估價值。

max選當前棋局結點的估價值最大的那個子節點,作為此步行棋的走法。
虛擬碼如下(

參考這裡):

function minimax(node, depth)
    if node is a terminal node or depth = 0
        return the heuristic value of node
    if the adversary is to play at node
        let α := +∞
        foreach child of node
            α := min(α, minimax(child, depth-1))
    else {we are to play at node}
        let α := -∞
        foreach child of
node α := max(α, minimax(child, depth-1)) return α

alpha和beta剪枝
若結點x是min結點,其兄弟結點中,已經求出來的最大估價值是b(有些兄弟結點還沒有算),那麼在對x的子節點進行考察的時候,一旦發現x的某子節點的估價值小於等於b,則不必在考察後邊x的結點了。這裡被剪掉的是x結點的子節點,也就是極大結點。同理對應的還有beta剪枝,剪掉的是極小結點。

二,例題
poj1568 Find the Winning Move
給定一個4*4的四子連珠棋盤的局面,現在輪到x先走,請問x下在哪裡可以必勝。

....
.xo.
.ox.
....

在窮盡搜尋中,而非搜幾層就停止的情況下,估價函式只有三種取值,正(inf),0,負(-inf),分別代表己方勝,平,負。

必勝是無論對手怎麼走,自己最後都能找到獲勝辦法,不是自己無論怎麼走都能獲勝。

如果能找到一個走法,其對應的結點估價值是inf,則就是一個必勝的策略。
具體程式碼如下:

/*************************************************************************
    > File Name: 1568.cpp
    > Author: gwq
    > Mail: [email protected] 
    > Created Time: 2015年09月01日 星期二 14時41分57秒
 ************************************************************************/

#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>

#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())

using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef pair<int, int> pii;
typedef long long ll;

const double esp = 1e-5;

#define N 5

int ansx, ansy, cnt;
char mp[N][N], tp[N];

int max_search(int x, int y, int beta);

void print(void)
{
    for (int i = 0; i < 4; ++i) {
        printf("%s\n", mp[i]);
    }
}

bool check(int x, int y)
{
    bool flag = true;
    for (int i = 0; i < 4; ++i) {
        if (mp[x][i] != mp[x][y]) {
            flag = false;
            break;
        }
    }
    if (flag) {
        return true;
    }
    flag = true;
    for (int i = 0; i < 4; ++i) {
        if (mp[i][y] != mp[x][y]) {
            flag = false;
            break;
        }
    }
    if (flag) {
        return true;
    }
    int c = 0;
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            if (i + j == x + y) {
                if (mp[i][j] == mp[x][y]) {
                    ++c;
                }
            }
        }
    }
    if (c == 4) {
        return true;
    }
    c = 0;
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            if (i - j == x - y) {
                if (mp[i][j] == mp[x][y]) {
                    ++c;
                }
            }
        }
    }
    if (c == 4) {
        return true;
    } else {
        return false;
    }
}

int min_search(int x, int y, int alpha)
{
    int ans = INF;

    if (check(x, y)) {
        return INF;
    }
    if (cnt == 16) {
        return 0;
    }
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            if (mp[i][j] == '.') {
                mp[i][j] = 'o';
                ++cnt;
                int tmp = max_search(i, j, ans);
                ans = min(ans, tmp);
                mp[i][j]= '.';
                --cnt;
                if (ans <= alpha) {
                    return ans;
                }
            }
        }
    }
    return ans;
}

int max_search(int x, int y, int beta)
{
    int ans = -INF;

    if (check(x, y)) {
        return -INF;
    }
    if (cnt == 16) {
        return 0;
    }

    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            if (mp[i][j] == '.') {
                mp[i][j] = 'x';
                ++cnt;
                int tmp = min_search(i, j, ans);
                ans = max(ans, tmp);
                mp[i][j]= '.';
                --cnt;
                if (ans >= beta) {
                    return ans;
                }
            }
        }
    }
    return ans;
}

bool solve(void)
{
    int alpha = -INF;
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            if (mp[i][j] == '.') {
                mp[i][j] = 'x';
                ++cnt;
                int tmp = min_search(i, j, alpha);  // X走過之後O走
                mp[i][j] = '.';
                --cnt;
                if (tmp == INF) {
                    ansx = i;
                    ansy = j;
                    return true;
                }
            }
        }
    }
    return false;
}

int main(int argc, char *argv[])
{
    while (scanf("%s", tp) != EOF) {
        if (tp[0] == '$') {
            break;
        }
        cnt = 0;
        for (int i = 0; i < 4; ++i) {
            scanf("%s", mp[i]);
            for (int j = 0; j < 4; ++j) {
                if (mp[i][j] != '.') {
                    ++cnt;
                }
            }
        }

        if (cnt <= 4) {     // 這一步直接從2S+到0ms,然而並不知道為啥
            printf("#####\n");
            continue;
        }

        if (solve()) {
            printf("(%d,%d)\n", ansx, ansy);
        } else {
            printf("#####\n");
        }
    }
    return 0;
}

poj 1085 Triangle War
兩個遊戲者輪流填充虛線三角形,每次只能填充一條短邊,若某遊戲者填充一條短邊後組成了一個小三角形,則該遊戲者擁有這個三角形,並且可以繼續填充。

當所有邊都被填充之後,擁有三角形數目多的遊戲者獲勝。給定一個局面,問誰可以贏。

三角形的個數是9,當一個人擁有5個及以上個三角形是這個人就必勝。這裡的估價函式定義為a的三角形數-b的三角形數。
程式碼如下:

/*************************************************************************
    > File Name: 1085.cpp
    > Author: gwq
    > Mail: [email protected] 
    > Created Time: 2015年09月01日 星期二 16時35分54秒
 ************************************************************************/

#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>

#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())

using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef pair<int, int> pii;
typedef long long ll;

const double esp = 1e-5;

#define N 20

int num[N], who, n, cnt, ans[2];
int dx[] = {1, 1, 2, 2, 2, 3, 3, 4, 5, 4, 4, 5, 5, 6, 6, 7, 8, 9};
int dy[] = {2, 3, 3, 4, 5, 5, 6, 5, 6, 7, 8, 8, 9, 9, 10, 8, 9, 10};
int mp[][N] = {
    {0, 1, 2},
    {2, 4, 5},
    {3, 4, 7},
    {5, 6, 8},
    {7, 10, 11},
    {11, 12, 16},
    {8, 12, 13},
    {9, 10, 15},
    {13, 14, 17}
};

int getid(int a, int b)
{
    int aa = min(a, b);
    int bb = max(a, b);
    for (int i = 0; i < 18; ++i) {
        if (aa == dx[i] && bb == dy[i]) {
            return i;
        }
    }
    return -1;
}

// 檢查三角形的時候,需要注意,因為加入的時候可能組成兩個三角形
int check(int id)
{
    int ret = 0;
    for (int i = 0; i < 9; ++i) {
        if (mp[i][0] == id || mp[i][1] == id || mp[i][2] == id) {
            int flag = 0;
            for (int j = 0; j < 3; ++j) {
                flag += num[mp[i][j]];
            }
            if (flag == 3) {
                ret++;
            }
        }
    }
    return ret;
}

int min_search(int alpha, int a, int b);

int max_search(int beta, int a, int b)
{
    if (cnt == 18) {
        return a > b ? INF : -INF;
    }
    if (a >= 5) {
        return INF;
    }
    if (b >= 5) {
        return -INF;
    }

    int tmp = -INF;
    for (int i = 0; i < 18; ++i) {
        if (!num[i]) {
            num[i] = 1;
            ++cnt;
            // 改變後需要恢復,索性存起來
            int aa = a;
            int bb = b;
            int cc = check(i);
            aa += cc;
            if (cc) {
                // 這裡傳入的值需要注意,這個的父節點是max,
                // 所以這裡的剪枝需要beta,而不是tmp,即當前
                // min結點獲得的最大值。下邊類似
                tmp = max(max_search(beta, aa, bb), tmp);
            } else {
                tmp = max(min_search(tmp, aa, bb), tmp);
            }
            num[i] = 0;
            --cnt;
            if (tmp >= beta) {
                return tmp;
            }
        }
    }
    return tmp;
}

int min_search(int alpha, int a, int b)
{
    if (cnt == 18) {
        return a > b ? INF : -INF;
    }
    if (a >= 5) {
        return INF;
    }
    if (b >= 5) {
        return -INF;
    }

    int tmp = INF;
    for (int i = 0; i < 18; ++i) {
        if (!num[i]) {
            num[i] = 1;
            ++cnt;
            int aa = a;
            int bb = b;
            int cc = check(i);
            bb += cc;
            if (!cc) {
                tmp = min(max_search(tmp, aa, bb), tmp);
            } else {
                tmp = min(min_search(alpha, aa, bb), tmp);
            }
            --cnt;
            num[i] = 0;
            if (tmp <= alpha) {
                return tmp;
            }
        }
    }
    return tmp;
}

int main(int argc, char *argv[])
{
    int t;
    int cs = 0;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        clr(num, 0);
        clr(ans, 0);
        who = 0;
        cnt = n;
        for (int i = 0; i < n; ++i) {
            int a, b;
            scanf("%d%d", &a, &b);
            int id = getid(a, b);
            num[id] = 1;
            int cc = check(id);
            if (cc) {
                ans[who] += cc;
            } else {
                who = !who;
            }
        }
        int ret = 0;
        if (!who) {
            ret = max_search(INF, ans[0], ans[1]);
        } else {
            ret = min_search(-INF, ans[0], ans[1]);
        }
        printf("Game %d: %c wins.\n", ++cs, ret == INF ? 'A' : 'B');
    }
    return 0;
}

相關推薦

poj 1085 Triangle War 1568 Find the Winning Move 極大搜尋 alpha-beta剪枝

一,極大極小搜尋及alpha-beta剪枝(參考這裡) 在博弈搜尋中,比如:圍棋,五子棋,象棋等,結果有三種可能:勝利,失敗和平局。 理論上可以窮舉所有的走法,這就需要生成整棵博弈樹。實際上不可行。因此搜尋時可以限制博弈樹的深度,到達該深度則不再往下搜,相當

POJ Find the Winning Move【minmax搜索+alpha-beta剪枝】【北大ACM/ICPC競賽訓練】

目前 剪枝 find 最大 namespace row 競賽 move icpc 1 #include<iostream> 2 using namespace std; 3 4 int row,col,chess; 5 char bo

POJ 1085 Triangle War極大搜尋+alpha-beta剪枝

// // main.cpp // Richard // // Created by 邵金傑 on 16/8/29. // Copyright © 2016年 邵金傑. All rights reserved. // #include<iostream&g

【叠代博弈+搜索+剪枝poj-1568--Find the Winning Move

() spa class 勝利 hid iat media gif nes poj 1568:Find the Winning Move 【叠代博弈+搜索+剪枝】 題面省略。。。 Input The input contains one or more test c

POJ 1568 Find the Winning Move

blank ble tdi ref return print bsp sca 在哪裏 Find the Winning Move 鏈接 題意: 4*4的棋盤,給出一個初始局面,問先手有沒有必勝策略?   有的話輸出第一步下在哪裏,如果有多個,按

[poj 1568]Find the Winning Move

Find the Winning Move Time Limit: 3000MS Memory Limit: 32768K Total Submissions:1782 Accepted

1568 Find the Winning Move 極大搜尋+alpha-beta剪枝

題目:在一個4*4的格子裡面,x和o兩個人玩遊戲,x畫'x',o畫'o',x先手,給定x和o都已經畫了一定步數的局面,問x是不是必勝的,如果是,輸出他應該畫在哪個位置 思路:極小極大搜尋+alpha-beta剪枝 程式碼: #pragma comment(linker, "

poj 1085 Triangle War (狀壓DP+記憶化搜尋+博弈)

Triangle War is a two-player game played on the following triangular grid:  Two players, A and B, take turns filling in any dotted li

博弈(alpha-beta 剪枝POJ —— 1085 Triangle War

Triangle War Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3066 Accepted: 1207 Description Triangle War is a two-pl

POJ 1085 Triangle War(博弈,極大搜尋+alpha_beta剪枝

題目:給出10個點,總共有18條邊,每次兩個人輪流加入一條邊,如果形成一個三角形,則三角形歸他所有,而且可以額外再走一步。最後三角形多的人勝 博弈問題 所謂的極大極小搜尋,其實就是搞個估價函式。然後主角肯定選個估價函式最大的,即對自己最有利的局面走。 而輪到對方的時候,

poj 1085 Triangle War (狀壓+記憶化搜索)

popu for align 技術 min \n most n) let Triangle War Time Limit:?1000MS ? Memory Limit:?655

杭電-1599 find the mincost route(最環有向圖)

find the mincost route Time Limit: 1000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 4164   

leetcode 486. Predict the Winner (Alpha-Beta剪枝實現關鍵點總結)

題意 小的博弈遊戲,兩個人輪流從一個數組的兩端取數,直到取完,最後取的和最大的人獲勝。問先手能否贏?其中如果和相同,先手勝。 思路 首先如果有偶數個元素,先手必勝,這個可以參考leetcode 877題求解思路,證明連結 奇數個的時候就沒有這麼好的

poj 1426 Find The Multiple

lines code span cti its case sig pac unsigned id=1426">poj 1426 的傳送門 Language: Find The Multiple Time Limit: 1000MS Mem

POJ 1426 Find The Multiple &amp;&amp; 51nod 1109 01組成的N的倍數 (BFS + 同余模定理)

ase 正整數 ng- eof ger put emp lan respond Find The Multiple Time Limit: 1000MS Memory Limit: 10000K Total Submissio

POJ 1426 Find The Multiple(DFS,BFS)

ons pro sum 數字 there lin queue hat 一個數 Given a positive integer n, write a program to find out a nonzero multiple m of n whose decimal

POJ 1426 Find The Multiple(數論——中國同余定理)

定義 十進制 pro desc decimal tput one return solution 題目鏈接: http://poj.org/problem?id=1426 Description Given a positive integer n, write a pro

POJ 1426 -- Find The Multiple

二叉搜索樹 center pan include false esp while 搜索 amp POJ 1426 -- Find The Multiple 大致題意: 給出一個整數n,(1 <= n <= 200)。求出任意一個它的倍數m,要求m必須只由十進制的

poj-1426-Find The Multiple(打表水過)

poj != AI pac IT ostream multipl tdi stream 思路: 2的最近可以整除的數是10 所以,很關鍵的一點,只要是偶數,例如: 6:2*3,可以拆分為能夠被2整除和能夠被3整除的乘積,所以,10*111=1110 144:72*2

POJ 1426 Find The Multiple(大數取模)【DFS】||【BFS】

++ printf true pty ace bfs 還要 ems 兩種 <題目鏈接> 題目大意: 給一個小於200的正整數n,問只有0和1組成的位數小於100的最小能被n整除的數是多少。 解題分析: 用DFS或者BFS沿著位數進行搜索,每一次搜索到下一位都有兩