【题解】CF1284G Seollal

前置芝士:拟阵交

如果你会,请跳过。

一切证明都略去了……如果需要的话我以后整理一个详细点的吧……

拟阵的定义

我们记一个拟阵(Matroid) \(\mathcal M = \langle S,\mathcal I\rangle\)

其中,我们称 \(S\)Ground Set\(\mathcal I\)Family of Sets

请注意此处的 \mathcal I 与后文中的 I 的区别(即 \(\mathcal I\)\(I\) 的区别)。

其中,\(\mathcal I\) 满足 \(\mathcal I \subseteq 2^S\)

或者你可以这么理解:一个拟阵描述了 \(S\) 中的子集的情况,包含在 \(\mathcal I\) 中即独立,反之不独立。

拟阵的公理

一个拟阵必须满足下面的 \(2\) 个条件:

  1. 对于所有的 \(I \in \mathcal I, I' \subseteq I\),则有 \(I' \in \mathcal I\)
  2. \(A,B \in \mathcal I, |A| < |B|\),则存在 \(x \in B \setminus A, A + \{x\} \in \mathcal I\)

图拟阵

\(S\) 是所有的边,\(\mathcal I\) 是所有的生成树。

拟阵交

问题:我们有 \(2\) 个拟阵 \(\mathcal M_1 = \langle S,\mathcal I_1\rangle, \mathcal M_2 = \langle S,\mathcal I_2\rangle\),求 \(I \subseteq S \in \mathcal I_1 \cap \mathcal I_2\),满足 \(I\) 中的所有元素在两个拟阵之中都独立。

算法大概长这样:

  1. \(I = \emptyset\)
  2. 建一个图 \(G_I(I \cup (S \setminus I), E)\),满足 \(\begin{cases}x \in I, y \in (S \setminus I), \langle x, y \rangle \in E \Leftrightarrow I - x + y \in \mathcal{I}_1 \\ x \in I, y \in (S \setminus I), \langle x, y \rangle \in E \Leftrightarrow I - x + y \in \mathcal{I}_2 \end{cases}\)
  3. 寻找图中的从 \(y_1\)\(y_{k+1}\) 的最短路,满足 \(I+y_1 \in \mathcal I_1\)\(I+y_2\in \mathcal I_2\)。记 \(x\)\(y\) 的最短路径为 \(\operatorname{path}(x,y)\)
  4. \(I\) 换成 \(I \oplus \{y_1, x_1, \dots, x_k, y_{k + 1}\}\).

时间复杂度为 \(\mathcal O(|S|^3)\)

Analysis

题目中所说的“只有一条简单路径”,即我们需要构造一棵生成树,且要满足叶节点的颜色一定是白色。

我们注意到黑色的节点只与白色节点相邻,所以黑色的点集和白色的点集是 \(2\) 个独立集。

下面有一个和显然今天下大雨一样显然的结论。

能找到一个无环的子图使得所有的黑点的度都为 \(2\) 是答案存在的充必条件。

由于它是无环子图,则我们只需无脑添上一些边就可以使得他变成一颗生成树。而树的叶子节点的度数为 \(1\),所以添边后形成的生成树的符合条件的。

如果存在答案,我们将生成树中的一些边删去,则我们一定可以得到一个无环子图使得黑点的度都是 \(2\)\(\blacksquare\)

接下来我们考虑怎么处理这个问题。

我们先从“无环”入手。

考虑图拟阵的定义:\(\mathcal M_1 = \langle S,\mathcal I_1\rangle\),其中 \(S\) 为所有边的集合,\(\mathcal I_1\) 为所有的生成树的集合。我们所要求的无环子图是我们所要求的生成树里面的一部分,所以这个无环子图在图拟阵 \(\mathcal M_1\) 里一定是独立的,而有环的子图一定是不独立的。

但是,度数这个条件怎么满足?

我们发现度数恰好为 \(2\) 是很难构造的,但是如果我们有度数 \(\le 2\),判断是否符合要求是很简单的。

我们考虑构造一个拟阵 \(\mathcal M_2 = \langle S,\mathcal I_2\rangle\),其中 \(S\) 仍然为所有边的集合(废话,否则咋拟阵交),而 \(\mathcal I_2\) 需要满足所有黑点度数 \(\le 2\) 的方案都独立。

然后我们算一下 \(\mathcal M_1\)\(\mathcal M_2\) 的交就可以啦!

边数为 \(\mathcal O(nm)\),所以总的时间复杂度为 \(\mathcal O(n^3m^3)\)

啊哈,是不是蒯个拟阵交的板子就好了呢~

然而不是的,因为细节贼多,贼容易写挂……

核心Code(void io::pc(char) 是输出,dsu是封装好的并查集):

class Matroid
{
private:
  const int fx[4] = {-1, 0, 0, 1};
  const int fy[4] = {0, -1, 1, 0};

  struct tuple
  {
    int x, y;
  };

  int n0, n1, n, m, demands;
  int count[801], dist[801], prev[801], que[801];
  bool vis[805], ok1[801], ok2[801];
  char grid[21][21], ans[41][41];
  tuple E[801];

#define make_tuple(x, y) ((tuple){x, y})
#define id(x, y) ((x)*n1 + y)
#define valid(x, y) ((x + y) > 0 && (x + y) % 2 == 0)
#define checkx(x) (x >= 0 && x < n0)
#define checky(x) (x >= 0 && x < n1)

  IL void get_edges(int i, int j)
  {
    for (int k = 0; k < 4; ++k)
    {
      int u = i + fx[k], v = j + fy[k];
      if (checkx(u) && checky(v) && grid[u][v] == 'O')
        E[m++] = make_tuple(id(i, j), id(u, v));
    }
  }

  bool augument()
  {
    dsu.init(n);
    memset(count, 0, sizeof(count));
    memset(dist, -1, sizeof(dist));
    for (int i = 0; i < m; ++i)
      if (vis[i])
      {
        dsu.merge(E[i].x, E[i].y);
        ++count[E[i].x];
      }
    int tail = 0;
    for (int i = 0; i < m; ++i)
    {
      ok1[i] = !vis[i] && dsu.find(E[i].x) != dsu.find(E[i].y);
      ok2[i] = !vis[i] && count[E[i].x] < 2;
      if (ok1[i])
      {
        dist[i] = 0, prev[i] = -1, que[tail++] = i;
        if (ok2[i])
        {
          vis[i] = true;
          return true;
        }
      }
    }
    for (int head = 0; head < tail; ++head)
    {
      int u = que[head];
      if (ok2[u])
      {
        while (~u)
          vis[u] ^= 1, u = prev[u];
        return true;
      }
      if (vis[u])
      {
        dsu.init(n);
        for (int i = 0; i < m; ++i)
          if (vis[i] && i != u)
            dsu.merge(E[i].x, E[i].y);
        for (int v = 0; v < m; ++v)
          if (!vis[v] && dsu.find(E[v].x) != dsu.find(E[v].y) && dist[v] == -1)
            dist[v] = dist[u] + 1, prev[v] = u, que[tail++] = v;
      }
      else
        for (int v = 0; v < m; ++v)
          if (vis[v] && count[E[u].x] - (E[u].x == E[v].x) < 2 && dist[v] == -1)
            dist[v] = dist[u] + 1, prev[v] = u, que[tail++] = v;
    }
    return false;
  }

public:
  void solve()
  {
    n0 = io::in(), n1 = io::in();
    for (int i = 0; i < n0; ++i)
      io::gets(grid[i]);
    n = n0 * n1, m = demands = 0;
    for (int i = 0; i < n0; ++i)
      for (int j = 0; j < n1; ++j)
        if (grid[i][j] == 'O' && valid(i, j))
          demands += 2, get_edges(i, j);
    while (augument())
      --demands;
    if (demands)
      pc('N'), pc('O'), pc('\n');
    else
    {
      pc('Y'), pc('E'), pc('S'), pc('\n');
      get_edges(0, 0);
      dsu.init(n);
      for (int t = 0; t < 2; ++t)
        for (int i = 0; i < m; ++i)
        {
          bool connected = dsu.find(E[i].x) == dsu.find(E[i].y);
          if (t && !connected)
            vis[i] = true;
          if (vis[i] && !connected)
            dsu.merge(E[i].x, E[i].y);
        }
      for (int i = 0; i < n0 * 2 - 1; ++i)
        for (int j = 0; j < n1 * 2 - 1; ++j)
          ans[i][j] = ' ';
      for (int i = 0; i < n0; ++i)
        for (int j = 0; j < n1; ++j)
          ans[i * 2][j * 2] = grid[i][j];
      for (int i = 0; i < m; ++i)
        if (vis[i])
        {
          int s = min(E[i].x, E[i].y);
          int s1 = max(E[i].x, E[i].y);
          if (s + 1 == s1)
            ans[s / n1 * 2][s % n1 * 2 + 1] = 'i';
          else
            ans[s / n1 * 2 + 1][s % n1 * 2] = '!';
        }
      for (int i = 0; i < n0 * 2 - 1; ++i)
      {
        for (int j = 0; j < n1 * 2 - 1; ++j)
          pc(ans[i][j]);
        pc('\n');
      }
    }
  }
};
posted @ 2021-12-16 08:01  zghtyarecrenj  阅读(52)  评论(0编辑  收藏  举报