首页 > 其他分享 >P1971 [NOI2011] 兔兔与蛋蛋游戏 题解

P1971 [NOI2011] 兔兔与蛋蛋游戏 题解

时间:2024-06-22 20:33:07浏览次数:20  
标签:蛋蛋 必经 int 题解 兔兔 kMaxN dep NOI2011

Description

这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。

这个游戏是在一个 \(n\) 行 \(m\) 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。

每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:

  • 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。
  • 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。

第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第 \(x\) 行第 \(y\) 列中的棋子移进空格中”记为 \(M(x,y)\)。

例如下面是三个游戏的例子。

最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。

注意:

  • 两个格子相邻当且仅当它们有一条公共边。
  • 兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。

\(1\leq n,m\leq 40\)。

Solution

考虑什么样的情况是必胜状态。

先有一个性质,就是一个位置最多被空格经过一次。

因为如果经过 \((x,y)\) 两次,则这两次之间的操作次数一定为偶数,那么如果最开始空格走到一个原本为黑色的格,那么 \((x,y)\) 就变成了黑色,而经过偶数次后再要回到 \((x,y)\) 必须要求 \((x,y)\) 为白色,矛盾。

所以一个位置最多被经过一次。

那么按照初始状态染色,黑格或者空格染黑,白格染白,那么每次一定是走到相邻的且未经过的颜色不同的格子。

按照这个颜色进行黑白染色就变成一个二分图的问题,即:有一个起点 \(s\),每次走到有连边的未经过的点,走不了的输,问谁有必胜策略。

有个结论是先手必胜等价于起点是最大匹配的必经点

证明就是如果起点是必经点,设走到 \(x\),\(x\) 的下一步一定是个必经点,否则 \(x\) 连下一步的点一定构成一个新的最大匹配。所以先手每步都会走到必经点,所以其必定能走到它的匹配点。而后手到最后总会先走不动,所以先手必胜。

如果起点是非必经点,则下一步一定走到必经点,否则也能构造出一个更大的匹配。然后就变成了起点是必经点的问题,所以这里先手必败。


所以题目就变成了每次删点,然后判断一个点是否为最大匹配必经点。考虑倒着做,每次加点然后在残量网络上跑网络流,如果有更新则为必经点。

然后这题就做完了。

时间复杂度:\(O(knm\sqrt{nm})\)。

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2e3 + 5, kMaxM = 1e5 + 5;
const int kD[][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

int n, m, k;
int col[45][45], x[kMaxN], y[kMaxN], cnt[kMaxN];
bool del[kMaxN];

int getid(int x, int y) { return (x - 1) * m + y; }

namespace Dinic {
struct Node {
  int v, w, pre;

  Node(int _v = 0, int _w = 0, int _pre = 0) : v(_v), w(_w), pre(_pre) {}
} e[kMaxM];

int n, s, t, tot = 1, tail[kMaxN], cur[kMaxN], dep[kMaxN];
bool vis[kMaxN];

void addedge(int u, int v, int w) { e[++tot] = {v, w, tail[u]}, tail[u] = tot; }
void add(int u, int v, int w) { addedge(u, v, w), addedge(v, u, 0); }
void init(int _n, int _s, int _t) {
  n = _n, s = _s, t = _t, tot = 1, std::fill_n(tail, n + 1, 0);
}

bool bfs() {
  for (int i = 1; i <= n; ++i)
    cur[i] = tail[i], vis[i] = 0, dep[i] = 1e9;
  std::queue<int> q;
  q.emplace(s), dep[s] = 0, vis[s] = 1;
  for (; !q.empty();) {
    int u = q.front(); q.pop();
    for (int i = tail[u]; i; i = e[i].pre) {
      int v = e[i].v, w = e[i].w;
      if (w && !vis[v]) {
        dep[v] = dep[u] + 1, vis[v] = 1, q.emplace(v);
      }
    }
  }
  return vis[t];
}

int dfs(int u, int lim) {
  if (u == t || !lim) return lim;
  int flow = 0;
  for (int &i = cur[u]; i; i = e[i].pre) {
    int v = e[i].v, w = e[i].w;
    if (w && dep[v] == dep[u] + 1) {
      int fl = dfs(v, std::min(lim, w));
      if (!fl) dep[v] = 1e9;
      e[i].w -= fl, e[i ^ 1].w += fl;
      lim -= fl, flow += fl;
      if (!lim) break;
    }
  }
  return flow;
}

int maxflow() {
  int ans = 0;
  for (; bfs(); ans += dfs(s, 1e9)) {}
  return ans;
}
}  // namespace Dinic

void dickdreamer() {
  std::cin >> n >> m;
  int s = n * m + 1, t = n * m + 2;
  Dinic::init(n * m + 2, s, t);
  // 0 : 白,1 : 黑
  for (int i = 1; i <= n; ++i) {
    std::string str;
    std::cin >> str;
    str = " " + str;
    for (int j = 1; j <= m; ++j) {
      if (str[j] == '.') x[0] = i, y[0] = j, del[getid(i, j)] = 1;
      if (str[j] == 'O') col[i][j] = 0;
      else col[i][j] = 1;
      if (!col[i][j]) Dinic::add(s, getid(i, j), 1);
      else Dinic::add(getid(i, j), t, 1);
    }
  }
  std::cin >> k;
  for (int i = 1; i <= 2 * k; ++i) {
    std::cin >> x[i] >> y[i];
    del[getid(x[i], y[i])] = 1;
  }
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) {
      if (col[i][j] || del[getid(i, j)]) continue;
      for (auto [di, dj] : kD) {
        int ti = i + di, tj = j + dj;
        if (col[ti][tj] && !del[getid(ti, tj)])
          Dinic::add(getid(i, j), getid(ti, tj), 1);
      }
    }
  }
  cnt[2 * k + 1] = Dinic::maxflow();
  for (int i = 2 * k; ~i; --i) {
    int u = getid(x[i], y[i]);
    del[u] = 0;
    for (auto [di, dj] : kD) {
      int tx = x[i] + di, ty = y[i] + dj, v = getid(tx, ty);
      if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
      if (col[tx][ty] != col[x[i]][y[i]] && !del[v]) {
        if (!col[x[i]][y[i]]) Dinic::add(u, v, 1);
        else Dinic::add(v, u, 1);
      }
    }
    cnt[i] = Dinic::maxflow();
  }
  std::vector<int> vec;
  for (int i = 1; i <= k; ++i) {
    if (cnt[2 * i - 2] && cnt[2 * i - 1])
      vec.emplace_back(i);
  }
  std::cout << vec.size() << '\n';
  for (auto x : vec) std::cout << x << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

标签:蛋蛋,必经,int,题解,兔兔,kMaxN,dep,NOI2011
From: https://www.cnblogs.com/Scarab/p/18262674

相关文章

  • P10538 [APIO2024] 星际列车 题解
    题意:有\(n\)个行星,编号为\(0\simn-1\)。有\(m\)辆星际列车,第\(i\)辆列车在时刻\(a_i\)从行星\(x_i\)出发,在时刻\(b_i\)到达行星\(y_i\),代价为\(c_i\)。换乘条件为上一辆车的终点和下一辆车的起点相同,且上一辆车到达时刻\(\le\)下一辆车出发时刻。你需要吃......
  • qoj8542 Add One 2 题解
    题目链接点击打开链接题目解法我们先考虑什么样的序列\(x_1,...,x_n\)是可以被得到的对于\(x_i>x_{i+1}\)的位置,我们需要至少对前缀\([1,i]\)做\(x_i-x_{i+1}\)次操作;对于\(x_{i-1}<x_{i}\)的位置,我们需要至少对后缀\([i,n]\)做\(x_i-x_{i-1}\)次操作我们需要......
  • [集训队互测 2023] 树哈希 题解报告
    [集训队互测2023]树哈希题解报告/bx/bx/bxzky!!!题意给定常数\(q\),定义一棵以\(1\)为根的有根树\(T\)的\(s(T)\)为\(T\)中本质不同的子树数量,定义其权值为\(q^{s(T)}\)。给定\(n\),对于\(i=1,\dots,n\)求所有大小为\(i\)的有标号有根树的权值之和对\(P\)......
  • CF1978E Computing Machine 题解
    好写程度:\(E>D>C\)。好想程度:\(C>D=E\)。总结:C是全场最难。我们考虑把两个操作对全体的\(a_i,b_i\)都做一遍,会发现我们只会做这两遍,不会再有嵌套的了,因为都做过一遍后\(\{a\}\)中0的数量只会减少,而且即使再做一遍也无法给\(\{b\}\)改成不一样的了,比较显然。下文中令......
  • [题解]AT_abc267_f [ABC267F] Exactly K Steps
    大家好,我是毒瘤,喜欢用玄学算法过题。发现题解区没有这个做法,于是来发一篇。思路不难发现如果一个点对\((u,v)\)的距离为\(d\),那么在这棵树以\(u\)为根时,\(v\)的深度为\(d\)。于是考虑换根DP。首先思考如何计算答案。显然我们可以将查询离线下来,然后当换根到以\(u\)......
  • BD202301·公园题解
    BD202301·公园题解考虑将整个移动过程分为两个部分:小度和度度熊汇合之前小度和度度熊汇合之后第一部分可以直接用Dijkstra算法直接搞定,第二部分可以考虑反向思考,从N点出发做一次Dijkstra,最后枚举每个汇合点即可得到答案。时间复杂度\(\Theta(nlogn)\)代码如下:#include......
  • [题解]AT_abc264_e [ABC264E] Blackout 2
    思路一道很经典的题,运用了一种叫「时光倒流」的技巧。「时光倒流」本质上就是将所有删边(或删点)的操作,通过倒序循环求值的方式转化为加边(或加点)。「时光倒流」具体实现通常伴随着并查集出现,维护一个连通块的某种性质。首先,我们需要将所有从始至终没有删过的边加入并查集。在这......
  • [题解]AT_abc263_d [ABC263D] Left Right Operation
    思路首先,不难发现最终的序列一定是形如下面的序列:\[l,\dots,l,a_i,a_{i+1},\dots,a_{i+j},r,\dotsr\]那么,我们就可以将其分为三段,每段都单独维护。首先,对于第一段,我们可以枚举出最后一个\(l\)的位置\(x\),那么和为\(x\timesl\)。对于第二段显然可以用前......
  • [题解]AT_abc256_h [ABC256Ex] I like Query Problem
    思路首先可以看一下P4145,在P4145中使用了一种叫势能线段树的Trick。对于势能线段树,我个人的理解是,对于一段区间(或一个点)直接暴力维护,在经过很少的次数后操作将没有意义的题就可以使用势能线段树。在本题中,如果没有推平操作,显然我们可以直接使用势能线段树,时间复杂度可以轻......
  • [题解]AT_abc263_f [ABC263F] Tournament
    先为大家毙掉一个错解思路首先不难发现,如果将整棵比赛的对战图画出来,一定是一个满二叉树。不妨将令一个节点\(u\)的左右儿子编号分别为\(2u\)和\(2u+1\)。然后定义\(dp_{u,d}\)表示将\(u\)为根的子树内的选手全部比赛完,并且\(u\)已经赢了\(d\)场的最大结果。发......