【题解】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\) 个条件:
- 对于所有的 \(I \in \mathcal I, I' \subseteq I\),则有 \(I' \in \mathcal I\)。
- 若 \(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\) 中的所有元素在两个拟阵之中都独立。
算法大概长这样:
- \(I = \emptyset\)
- 建一个图 \(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}\)。
- 寻找图中的从 \(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)\)。
- 把 \(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');
}
}
}
};