P10851 [EGOI2024] Make Them Meet / 活动面基
构造题做的太少了,感觉这题是非常厉害的构造题。
考虑菊花图。菊花图结构非常简单,我的做法是先让所有点颜色相同,这样如果两个人分别在叶子上就可以在根相遇,否则一定会有一人走到根上,再与叶子挨个匹配即可。
考虑链的做法。我们可以考虑把两个人往中间赶,他们肯定可以相遇。但是这种想法非常不好构造方案,想想有没有别的方法?转换思路,我们很难把人往中间赶,但是我们可以将他们都往同一方向走,到边上就会反弹,这样也能让他们相遇。
具体的,对于每一轮,奇数轮 \((1,2)\) 同色,\((3,4)\) 同色......偶数轮 \((2,3)\) 同色,\((4,5)\) 同色以此类推即可。我们发现两人都会往同一方向走。完全图也可以用这种方法。
然后考虑树的做法。有了链的方法,树的构造方法也可以很自然的想到。我们按深度奇偶性染色,向刚刚一样。我们还需对于树根 \(rt\) 找到一个儿子 \(son\),在每一轮都与根颜色相同,这样可以让达到根的人在 \(rt\) 和 \(son\) 之间来回走。
现在考虑将树的构造套到一般图上。
理清思路,考虑一般图要有哪些性质才能使用树的构造方法?
- 对于任意节点 \(u\),\(u\) 的任意两个儿子之间不能连边。
- 对于根节点 \(rt\),\(rt\) 不能与选出的 \(son\) 的任意儿子连边。
实际上是要寻找一个合法的生成树,显然按 dfs 序生成的树是可以完美的满足性质一的,如果跑出来的是一条链,则可以直接用链的构造方法。否则,我们要找到根,使得根满足性质二。
我们找到一个按 dfs 序生成的树的分叉点,使得该点子树内没有其它分叉点(即这个点下挂有若干条链)。直接找最深的就行。为什么要找到最深的?因为在接下来的步骤中会有不好处理的地方,之后会讲。
设这个点为 \(q\),其父亲为 \(p\),如果存在一个 \(q\) 的儿子 \(u\) 与 \(p\) 不连边,那么我们很幸运地找到了一个可行的根。我们可以将 \(u\) 设为根,并将其在原树下挂的链暂时删去,然后先从 \(u\) 走到 \(q\),再走完 \(q\) 原来的儿子,最后再走到 \(p\) 进行生成树的构造。生成完后将删去的链再挂回来即可。容易发现这样生成出来的树,树根是可以满足性质二的,选出的 \(son\) 即为 \(q\)。
如果 \(q\) 的所有儿子与 \(p\) 之间都有边,没关系,我们随便找一个 \(u\),作为新的根,dfs 构造树的时候先到 \(q\) 中的儿子进行搜索,由于 \(q\) 中的每个儿子与 \(p\) 都有连边,那么在新的树中 \(p\) 可以作为一个 \(q\) 的儿子的儿子,这样生成出来的树也能满足性质二。
生成完后怎么做?这就体现出找最深的分叉点的优越性了。我们注意到生成出来的树的根节点只有两个儿子,并且子树分别为一棵正常的树和一条链。我们采取一般性的方法,奇数轮让深度为 \(2,4,\cdots\) 的点与父亲同色,偶数论让深度为 \(3,5,\cdots\) 的点与父亲同色,为什么不让深度为 \(1\) 的点连向根?因为细心的你可以发现,我们无法保证根的两个儿子之间不存在边。那么我们考虑每次新增加一轮,在偶数轮之前,将链的顶与根这两个点同色。然后在原来的奇偶轮中,保证根 \(u\) 与 \(q\) 同色。
这样构造可以发现,若一个人在子树中,他上去会在 \(u\) 与 \(q\) 徘徊。而如果一个人在链中,他将会在 \(q\) 时有两条路走,一条是走到子树中,然后上来并在 \(u\) 与 \(q\) 徘徊。另一条是回到链,并在 \(q\) 到链底往返走。
来分析一下最劣情况,应该是两个人都在链中,其中一个先到根部然后进入子树,而另一个人则走回链底,他们刚好错开了。这样如果是最劣的话,设链的长度为 \(l\),那么经过的路径长度大概为 \(3l\),操作次数则为 \(3l\times 1.5=4.5l\),则操作次数复杂度为 \(\mathcal{O}(4.5n)\)。
终于做完啦!
代码:
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++ i)
#define rrp(i, l, r) for (int i = r; i >= l; -- i)
#define pii pair <int, int>
#define eb emplace_back
#define id(x, y) n * ((x) - 1) + (y)
#define ls p << 1
#define rs ls | 1
#define MN 1000000000000000000
using namespace std;
using namespace __gnu_cxx;
constexpr int N = 100 + 5, P = 1e9;
constexpr double PI = acos (-1.0);
inline int rd () {
int x = 0, f = 1;
char ch = getchar ();
while (! isdigit (ch)) {
if (ch == '-') f = -1;
ch = getchar ();
}
while (isdigit (ch)) {
x = (x << 1) + (x << 3) + ch - 48;
ch = getchar ();
}
return x * f;
}
int n, m;
int col[N];
bool e[N][N], vis[N];
void init () {
rep (i, 1, n) col[i] = i;
}
void put () {
rep (i, 1, n) printf ("%d ", col[i]);
puts ("");
}
int id[N], tot;
vector <int> t[N], G[N];
int dep[N], fa[N];
void dfs (int u) {
vis[u] = 1; id[++ tot] = u;
for (auto v : G[u]) {
if (vis[v]) continue;
dep[v] = dep[u] + 1; fa[v] = u; dfs (v);
}
rep (v, 1, n) {
if (! e[u][v] || vis[v]) continue;
dep[v] = dep[u] + 1; fa[v] = u; dfs (v);
}
}
int main () {
// freopen ("1.in", "r", stdin);
// freopen ("1.out", "w", stdout);
dep[0] = -1;
n = rd (), m = rd ();
int T = 420;
rep (i, 1, m) {
int u = rd () + 1, v = rd () + 1;
e[u][v] = e[v][u] = 1;
} dfs (1);
rep (i, 1, n) t[fa[i]].eb (i);
bool chk = 1;
rep (i, 1, n) {
if (t[i].size () > 1) chk = 0;
}
if (chk) {
cout << T << endl;
rrp (i, 1, T) {
init ();
rep (j, 2, n) {
if ((dep[id[j]] & 1) == (i & 1)) col[id[j]] = col[fa[id[j]]];
}
put ();
} return 0;
}
int q = 0, p = 0;
rep (i, 1, n) {
if (t[i].size () > 1) {
if (dep[q] < dep[i]) q = i, p = fa[i];
}
}
int u = t[q][0];
for (auto v : t[q]) if (! e[v][p]) u = v;
memset (vis, 0, sizeof vis);
if (! t[u].empty ()) {
int v = u;
while (! t[v].empty ()) v = t[v][0], vis[v] = 1;
}
G[u].eb (q);
G[q] = t[q];
if (p) G[q].eb (p);
tot = 0; fa[u] = 0;
dep[u] = 0; dfs (u);
if (! t[u].empty ()) {
int v = u;
while (! t[v].empty ()) dep[t[v][0]] = dep[v] + 1, v = t[v][0], id[++ tot] = v;
}
cout << T << endl;
rrp (i, 1, T) {
init ();
if ((i % 3) <= 1) col[q] = col[u];
rep (j, 3, n) {
if (((fa[id[j]] == u) ? 2 : (dep[id[j]] & 1)) == (i % 3)) col[id[j]] = col[fa[id[j]]];
}
put ();
}
return 0;
}