联合省选2023 D2T1 过河卒

发布时间 2023-04-24 15:51:32作者: jucason_xu

我们可以先 \(dp\),设 \(f_{i,j,k,l}\)\(g_{i,j,k,l}\)表示当前三个棋子分别在点 \(i,j,k\),目前轮到 \(l\) 走,谁胜利,最终会走多少步。

然后我们发现,变成一个有向图博弈。并且 \(l\) 是由 \(i,j,k\) 的奇偶性唯一确定的。就可以在图上直接做了。

首先我们发现,我们其实可以把初始的六个坐标编码成三个 \(i,j,k\),再编码成唯一的一个数对应图上的一个节点。并且最好不要解码出来。因为乘法编码解码的时候要取模,位运算编码占用多余空间大。

然后在可以转移到的状态之间连有向边。链式前向星存图有优势。

我们可以先处理出所有的终止状态:不同颜色点重合的、黑棋子到达终点的、某一方不能动的(也就是度数为 \(0\) 的)。

接下来,我们从所有的终止节点开始倒着 bfs。

一种做法是两次 bfs,在 bfs 的过程中,如果遇到了一定会的转移就转移上去,(例如红一定转移到红胜利的转移),而记录每个点的出度,除非所有能转移到的状态胜利方都和自己不一样,否则不会去转移。

然后再在有结果的方案中再 bfs 一遍,得到最终的步数。

但是我们发现这个其实可以一起做,因为两次都是 bfs,加入方法的判定是一致的。(因为 bfs 得到最终步数最优的证明是在第二种转移中,最后一个有贡献的转移一定是所有合法转移中步数最多的一个,符合败方的需求)我们就可以得到比较优美的做法。

贴一下个人觉得最好的 bfs 片段(by songjiaxing)


        while (l <= r) {
            int u = q[l++];

            if (a[u].f == 1)
                For(i, d[u], d[u + 1]) {
                int v = G[i];

                if (!a[v].f)
                    a[v].f = 2, a[v].g = a[u].g + 1, q[++r] = v;
                else
                    cmin(a[v].g, a[u].g + 1);
            } else
                For(i, d[u], d[u + 1]) {
                int v = G[i];

                if (a[v].f)
                    continue;

                a[v].g = max(a[v].g, a[u].g + 1);

                if (!--a[v].d)
                    a[v].f = 1, q[++r] = v;
            }
        }