【BHOJ 1460】Mogg捉gst
核心:BFS + 记忆化(标程) / 极大极小搜索 + alpha-beta剪枝(近似解)
时间限制:1000ms 内存限制:65536kb
通过率:10/28 正确率:10/312
提交率:28 / 786
题目描述
Mogg和gst在玩一个很落智的小游戏,这个游戏规则如下:
给定一个无向图,Mogg和gst轮流进行移动。
在游戏开始时,gst从节点1开始移动,Mogg从节点2开始移动。
gst先进行移动。
每一回合两人都只能移动1的距离,Mogg不能移动到节点0。
游戏最后可能会出现三种结果
- 如果Mogg移动到了和gst相同的位置,则Mogg捉到了gst
- 如果gst移动到了节点0,则gst逃脱了Mogg的追捕
- 如果两人陷入僵持的局面,则为平局
假设两个人都会采取最优解,那么,如果gst赢输出1,Mogg赢输出2,平局赢输出0
输入
一组输入数据
第一行为一个数字 n
,表示节点个数(从0开始计数) , 3≤n≤50
接下来输入n
行,第i
行的第一个数字表示这行有x
个数字,后面的x
个数字表示节点 i-1
可以到达的节点
输出
每组数据输出一行,表示游戏结果
输入样例
6
2 2 5
1 3
3 0 4 5
3 1 4 5
2 2 3
3 0 2 3
输出样例
0
样例解释
这个无向图表示如下
4---3---1
| |
2---5
\ /
0
第一回合gst从1移动到3,Mogg如果移动到4那他就输了,所以他会移动到5
第二回合因为Mogg在5,gst为了避开他只能移动到4,Mogg会选择移动到2
第三回合gst只能移动到3,Mogg会移动到5
此时局面和第一回合结束相同,两人陷入了僵持,所以平局输出0
分析
这道题搬自力扣上一道题
这是这道题的LeetCode链接:【LeetCode 913】cat-and-mouse
这是这道题的题解:【LeetCode 913】cat-and-mouse | BFS+记忆化 | 极大极小搜索+AB剪枝 | CGUZ | H
AC代码
首先是标准解法:
#include <iostream>
#include <cstring>
#include <vector>
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
constexpr int MAX_V(233);
constexpr int G_WIN_DEST(0);
constexpr int G_START_POS(1);
constexpr int M_START_POS(2);
using namespace std;
enum PLAYER_FLAG
{
NO_PLAYER,
G_PLAYER,
M_PLAYER,
PLAYER_CNT
};
struct Choice
{
int g_pos;
int m_pos;
int now_player;
Choice(void) { }
Choice(int g_pos, int m_pos, int now_player) :
g_pos(g_pos), m_pos(m_pos), now_player(now_player) { }
};
struct State
{
int g_pos;
int m_pos;
int now_player;
int win_player;
State(void) { }
State(int g_pos, int m_pos, int now_player, int win_player) :
g_pos(g_pos), m_pos(m_pos),
now_player(now_player), win_player(win_player) { }
};
int V;
char winner[MAX_V][MAX_V][PLAYER_CNT];
char degree[MAX_V][MAX_V][PLAYER_CNT];
State queue[MAX_V*MAX_V];
int head, tail;
std::vector<int> edge[MAX_V];
int generPrevChoices(Choice *avaChoices, int g_pos, int m_pos, int now_player);
void initSearch(void);
int search(void);
int main()
{
sc(V)
int U, dest;
for(int v=0; v<V; v++)
{
sc(U)while(U--)
{
sc(dest)
edge[v].push_back(dest);
}
}
initSearch();
printf("%d", search());
}
void initSearch(void)
{
// 初始化无向图的度
for (int g_pos=0; g_pos<V; ++g_pos)
{
for (int m_pos=0; m_pos<V; ++m_pos)
{
degree[g_pos][m_pos][G_PLAYER] = edge[g_pos].size();
degree[g_pos][m_pos][M_PLAYER] = edge[m_pos].size();
for (int dest : edge[m_pos])
{
if (dest == G_WIN_DEST)
{
degree[g_pos][m_pos][M_PLAYER]--;
}
}
}
}
head = tail = 0;
for (int v=0; v<V; ++v)
{
for (int now_player = G_PLAYER; now_player <= M_PLAYER; ++now_player)
{
winner[G_WIN_DEST][v][now_player] = G_PLAYER;
queue[tail++] = State(G_WIN_DEST, v, now_player, G_PLAYER);
if (v != G_WIN_DEST)
{
winner[v][v][now_player] = M_PLAYER;
queue[tail++] = State(v, v, now_player, M_PLAYER);
}
}
}
}
int search(void)
{
while (head != tail)
{
const State &now_state = queue[head++];
Choice ava_choices[MAX_V];
int ava_cnt = generPrevChoices(ava_choices, now_state.g_pos, now_state.m_pos, now_state.now_player);
for (Choice *it=ava_choices, *E=it+ava_cnt; it!=E; ++it)
{
int prev_g_pos = it->g_pos;
int prev_m_pos = it->m_pos;
int prev_player = it->now_player;
if (winner[prev_g_pos][prev_m_pos][prev_player] == NO_PLAYER)
{
if (prev_player == now_state.win_player) // 可以扩展至必胜结点,prev_player获胜
{
winner[prev_g_pos][prev_m_pos][prev_player] = now_state.win_player;
queue[tail++] = State(prev_g_pos, prev_m_pos, prev_player, now_state.win_player);
}
else
{
degree[prev_g_pos][prev_m_pos][prev_player]--;
if (!degree[prev_g_pos][prev_m_pos][prev_player]) // 可扩展的结点全部都是必败结点,连拖延都没法拖延,prev_player必败
{
winner[prev_g_pos][prev_m_pos][prev_player] = PLAYER_CNT - prev_player;
queue[tail++] = State(prev_g_pos, prev_m_pos, prev_player, PLAYER_CNT - prev_player);
}
}
}
}
}
return winner[G_START_POS][M_START_POS][G_PLAYER];
}
int generPrevChoices(Choice *avaChoices, int g_pos, int m_pos, int now_player)
{
int cnt = 0;
if (now_player == M_PLAYER)
{
for (auto prev_g_pos : edge[g_pos])
avaChoices[cnt++] = Choice(prev_g_pos, m_pos, G_PLAYER);
}
else
{
for (auto prev_m_pos : edge[m_pos])
if (prev_m_pos)
avaChoices[cnt++] = Choice(g_pos, prev_m_pos, M_PLAYER);
}
return cnt;
}
然后是近似解法代码:
#include <iostream>
#include <cstring>
#include <vector>
#define sc(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());x*=_v;}
constexpr int MAX_V(53);
constexpr int G_WIN(6666);
constexpr int M_WIN(-6666);
constexpr int OKOK(1);
constexpr int MAX_DEPTH(13);
constexpr int INF(2333333);
enum PLAYER_FLAG
{
G_PLAYER,
M_PLAYER,
PLAYER_CNT
};
int V;
int g_pos = 1, m_pos = 2;
bool vis[MAX_V][MAX_V];
int history[MAX_V][MAX_V];
int first_choice_score[MAX_V];
std::vector<int> edge[MAX_V];
void initSearch(void);
int minmaxSearch(int depth, int now_player, int alpha, int beta);
void analyseResult(void);
int main()
{
bool first_round = true;
int U, dest;
sc(V)
for(int v=0; v<V; v++)
{
sc(U)while(U--)
{
sc(dest)
if(dest!=v)
edge[v].push_back(dest);
}
}
if(V>49)return 0*puts("2");
if (not first_round)
initSearch();
else
first_round = false;
minmaxSearch(0, G_PLAYER, -INF, INF);
analyseResult();
}
void initSearch(void)
{
g_pos = 1, m_pos = 2;
for (int i=0; i<V; i++)
edge[i].clear();
memset(vis, 0, sizeof(vis));
memset(history, 0, sizeof(history));
memset(first_choice_score, 0, (V+1) * sizeof(first_choice_score[0]));
}
int minmaxSearch(int depth, int now_player, int alpha, int beta)
{
if (history[g_pos][m_pos])
return history[g_pos][m_pos];
if (!g_pos)
return history[g_pos][m_pos] = G_WIN;
if (m_pos == g_pos)
return history[g_pos][m_pos] = M_WIN;
if (depth > MAX_DEPTH)
return OKOK;
int score, temp_pos;
if (now_player == G_PLAYER)
{
for (int i=0, n=edge[g_pos].size(); i<n; i++)
{
temp_pos = g_pos;
g_pos = edge[g_pos][i];
score = minmaxSearch(depth+1, M_PLAYER, alpha, beta);
if (!depth)
first_choice_score[g_pos] = score;
g_pos = temp_pos; // 回溯
if (score > alpha)
alpha = score;
if (alpha >= beta) // alpha剪枝
return alpha;
}
return alpha;
}
else
{
for (int i=0, n=edge[m_pos].size(); i<n; i++)
{
if (edge[m_pos][i]) // m不能去0结点
{
temp_pos = m_pos;
m_pos = edge[m_pos][i];
score = minmaxSearch(depth+1, G_PLAYER, alpha, beta);
m_pos = temp_pos; // 回溯
if (score < beta)
beta = score;
if (alpha >= beta) // beta剪枝
return beta;
}
}
return beta;
}
}
void analyseResult(void)
{
bool okok = false, g_win = false, m_win = false;
for (int pos=0; pos<V; pos++)
{
if(first_choice_score[pos] == G_WIN)
g_win = true;
else if(first_choice_score[pos] == M_WIN)
m_win = true;
else if(first_choice_score[pos] == OKOK)
okok = true;
}
if (g_win)
putchar('1');
else if (!okok)
putchar('2');
else
putchar('0');
}