【算法基础】图上的博弈

本文为翻译文章
原文链接:https://cp-algorithms.com/game_theory/games_on_graphs.html

Games on Arbitrary Graphs

两个选手在任意的一个图 G G G上做游戏,当前游戏的状态表示为图上的一个点。两名选手轮流行动,每一次行动可以把当前的点移动到有一条边相连的位置上。根据游戏规则的不同,第一个不能移动的选手赢或者输。

我们考虑最一般的情况,即在一个带环的有向图上的游戏。给定一个初始的状态,假设两位选手都使用最好的策略,任务是判断谁能赢得游戏,或者这个游戏是平局。

这个问题可以被很高效地解决掉。我们可以用 O ( m ) O(m) O(m)的时间(m为图中边的数量)得到图上所有点作为初始点的结果。

算法描述

在两名选手都使用最佳策略的情况下,如果某个点作为初始点先手能获得胜利,那就把这个点叫做胜利点。类似的,如果某个点作为初始点,先手失败,那这个点就叫失败点。

图上所有没有出边的点,我们是可以马上判断出该点是胜利点还是失败点。

同时还有以下的判断规则:

  • 如果一个顶点存在一条到达失败点的边,那么这个点是一个胜利点。
  • 如果一个顶点的所有出边都会到达胜利点,那这个点就是一个失败点。
  • 如果最后还有一些点上面的两个条件都不符合,那么在两名选手都使用最佳的策略时,这个点作为起始点,游戏会平局。

我们马上就可以找到一个 O ( n m ) O(nm) O(nm)​的算法。就是要一遍又一遍地遍历所有结点,判断每个结点是否满足前两个条件,如果在一次遍历中再也找不到新得结点可以满足前两个条件,那么停止遍历。

这个和我们想要的时间复杂度相差太大,我们需要把时间复杂度降到 O ( m ) O(m) O(m)

首先我们先遍历一般所有的结点,找到那些开始就确定是胜利点和失败点的结点。从每个这样的点开始跑一遍dfs。dfs过程是从已知结点扩展到未知结点的过程。如果当前结点的上一个结点是失败点,那么就是上面的第一种情况,即这个结点是成功点。如果当前结点的上一个结点是成功点,还需要判断所有到达它的结点是否是成功点,这个过程可以通过增加一个degree数组在 O ( 1 ) O(1) O(1)的时间内实现,每次遇到当前的结点且上一个结点为胜利点时,degree数组减一,如果degree数组减到了0,那么说明该结点满足上面第二个条件。

对于每个成功点和失败点我们都访问1次,对于那些平局点我们不访问。总共的时间复杂度为 O ( m ) O(m) O(m)

代码实现

下面是用DFS的代码实现。其中变量adj_rev保存的是反向边。边 ( i , j ) (i,j) (i,j)adj_rev中保存为边 ( j , i ) (j,i) (j,i)。同时假设所有边的出度已经计算过了。

vector<vector<int>> adj_rev;

vector<bool> winning;
vector<bool> losing;
vector<bool> visited;
vector<int> degree;

void dfs(int v) {
    visited[v] = true;
    for (int u : adj_rev[v]) {
        if (!visited[u]) {
            if (losing[v])
                winning[u] = true;
            else if (--degree[u] == 0)
                losing[u] = true;
            else
                continue; 
            dfs(u);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值