\(\mathscr{Description}\)

  Link.

  给定两棵含 \(n\) 个结点的树 \(T_1=(V_1,E_1),T_2=(V_2,E_2)\),求一个双射 \(\varphi:V_1\rightarrow V_2\),使得 \(\forall (u,v)\in V_1^2,~(u,v)\notin E_1\lor (\varphi(u),\varphi(v))\notin E_2\),或声明无解。

  \(n\le10^4\)。

\(\mathscr{Solution}\)

  为方便叙述,若 \(T\) 中最大结点度数为 \(d\),那么称 \(T\) 为 \((n-1-d)\)-star,即一个 \(k\)-star 可以通过删去 \(k\) 个点变成菊花图。(或许外国友人叫它 star?)

  首先,当然是猜无解条件:显然一个充分条件是,若 \(T_1\) 或 \(T_2\) 是 \(0\)-star,则无解。受此启发,我们连带着考虑 \(1\)-star 是否有解 <motivation>。不妨设 \(T_1\) 是 \(k\)-star,\(T_2\) 是非 \(0\)-star 的任意树。直观感受,\(T_1\) 有一个限制极强的“花蕊”——在 \(\varphi\) 作用后它只能和一个结点连边,因此它必然被映到 \(T_2\) 的一片叶子上。此后的构造就很简单了,这里给出 tutorial 里的图(其中 \(w\) 即“花蕊”,\(\varphi(v)\) 是 \(\varphi(w)\) 唯一能连向的点):

  现在已经得到 \(1\)-star 的解,还是直观地想,比 \(1\)-star 限制弱的 \(2\)-star, \(3\)-star, … 是否都有解呢?我们可以尝试用归纳证明这一结论,一方面确认结论正确性,另一方面,归纳过程往往就展现了构造过程 <motivation>


  宽泛地讲,大致的归纳过程应是:在 \(V_1\) 和 \(V_2\) 中取出两个小点集 \(S_1,S_2\),保证 \(V_1\setminus S_1\) 和 \(V_2\setminus S_2\) 各自的诱导子图不是 \(0\)-star,并且在得到两个诱导子图的构造后,一定能够找到合法的 \(S_1\rightarrow S_2\)。为了归纳的简洁,我们尽量让 \(V_1\setminus S_1\) 和 \(V_2\setminus S_2\) 的诱导子图是树,继而要求 \(S_1,S_2\) 是当前 \(T_1,T_2\) 的叶子集 <motivation>;此外,为了保证有解,取出两片与同一结点邻接的叶子就显得很冒险——如果一者不合法,另一者也不可能替换它的映射,这种坏事应该避免 <motivation>。最终,可以汇总得到这样一个 motivation: \(S_1,S_2\) 分别是 \(T_1,T_2\) 中两片不邻接与同一结点的,删去后不会构成 \(0\)-star 的叶子。

  感性分析后,我们来理性证明。对于不是 \(0\)-star 或 \(1\)-star 的 \(T_1\) 与 \(T_2\),尝试在其中分别取出满足上述要求的叶子 \(\{u_1,v_1\}\subseteq V_1\) 和 \(\{u_2,v_2\}\subseteq V_2\),它们在各自树上的唯一临接点分别是 \(\{x_1,y_1\},\{x_2,y_2\}~(x_1\neq y_1,x_2\neq y_2)\)。对 \(V_1\setminus \{u_1,v_1\}\) 和 \(V_2\setminus\{u_2,v_2\}\) 进行归纳得到一个 \(\varphi\)。现考虑能否为 \(\varphi\) 定义 \(\{u_1,v_1\}\rightarrow\{u_2,v_2\}\):

  不妨令 \(\varphi(u_1):=u_2,\varphi(v_1):=v_2\),若此时 \(\varphi\) 不合法,那么 \((u_2,\varphi(x_1))\in E_2\lor (v_2,\varphi(y_1))\in E_2\),可见 \(\varphi(x_1)=x_2\lor\varphi(y_1)=y_2\),结合 \(\varphi\) 是双射这个基本条件,可知 \((v_2,\varphi(x_1))\notin E_2\land (u_2,\varphi(y_1))\notin E_2\),因此此时定义 \(\varphi(u_1):=v_2,\varphi(v_1):=u_2\) 一定合法。

  没画框框的原因是第一步加粗的“尝试”,以 \(T_1\) 为例,我们真的能取出 \(\{u_1,v_1\}\) 吗?

  不能!但是问题不大,可以说明当 \(T_1\) 不是 \(0\)-star 或 \(1\)-star 时,仅有 \(|V_1|=5\) 时有特例。鉴于这里是 OI 而非 MO <motivation>,我们可以大力讨论所有 \(|V_1|=|V_2|=5\) 并证明它们都有解,算法实现时大力全排列得到它们的解。综上,有原结论成立。 \(\square\)


  经过精细实现可以做到 \(\mathcal O(n\log n)\),由于担心有人 \(\mathcal O(n^2)\) 莽过了这里简单提一下。我们不选确定的根,而是动态维护最大度数的结点。对于每个结点,维护其当前与其邻接的叶子集 \(L_u\) 以及这棵树可用叶子集 \(L_T\),保证仅选取 \(L_u\) 中的一个元素加入 \(L_T\)。这样,从 \(L_T\) 任取两个点,简单判一下删去它们是否构成 \(0\)-star,若可行,则更新它们临接点的 \(L\) 集合,若发现邻接点也成了叶子就再加入邻接点的邻接点的 \(L\) 集合即可。这 \(n\) 挺小的,全程 std::set 乱飞应该没什么问题√

\(\mathscr{Code}\)

/*+Rainybunny+*/

#include <bits/stdc++.h>

#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i) typedef std::pair<int, int> PII;
#define fi first
#define se second const int MAXN = 1e4;
int n, deg[2][MAXN + 5], fa[2][MAXN + 5], ref[MAXN + 5];
// fa[t][u] is defined only when u is a leaf.
bool rsv[MAXN + 5];
std::set<PII> edge[2], mdeg[2];
std::set<int> lbuc[2];
std::vector<int> adj[2][MAXN + 5];
std::deque<int> ch[2][MAXN + 5]; inline bool exist(const int t, int u, int v) {
if (u > v) u ^= v ^= u ^= v;
return edge[t].count({ u, v });
} inline void init(const int t, const int u, const int las) {
if (las && deg[t][u] == 1) ch[t][fa[t][u] = las].push_front(u);
for (int v: adj[t][u]) if (v != las) {
if (!las && deg[t][u] == 1) ch[t][fa[t][u] = v].push_front(u);
init(t, v, u);
}
} inline bool check(const int u) {
for (int v: adj[0][u]) {
if (ref[v] > 0 && exist(1, ref[u], ref[v])) {
return false;
}
}
return true;
} inline void solve(const int rest) {
int rt0 = mdeg[0].rbegin()->se, rt1 = mdeg[1].rbegin()->se;
if (rest <= 5) { // detailed situation, brute force.
int p[6] = {}, q[6] = {};
rep (i, 1, n) {
if (!ref[i]) p[++p[0]] = i;
if (!rsv[i]) rsv[q[++q[0]] = i] = true;
}
do {
bool flg = true;
rep (i, 1, p[0]) ref[p[i]] = 0;
rep (i, 1, p[0]) {
ref[p[i]] = q[i];
if (!(flg = check(p[i]))) break;
}
if (flg) return ;
} while (std::next_permutation(q + 1, q + q[0] + 1));
assert(false); // since there's a solution existing, it's impossible.
} else if (deg[0][rt0] < rest - 2 && deg[1][rt1] < rest - 2) {
// choose two pairs of leaves.
assert(lbuc[0].size() >= 2 && lbuc[1].size() >= 2);
int u0(*lbuc[0].begin()), v0(*++lbuc[0].begin());
int u1(*lbuc[1].begin()), v1(*++lbuc[1].begin());
if (deg[0][rt0] == rest - 3 && fa[0][u0] != rt0)
v0 = ch[0][rt0].back();
if (deg[1][rt1] == rest - 3 && fa[1][u1] != rt1)
v1 = ch[1][rt1].back(); auto update = [](const int t, const int u)->void {
assert(u);
lbuc[t].erase(ch[t][u].back());
mdeg[t].erase({ deg[t][ch[t][u].back()]--, ch[t][u].back() });
mdeg[t].erase({ deg[t][u]--, u });
mdeg[t].insert({ deg[t][u], u });
ch[t][u].pop_back();
if (ch[t][u].size()) lbuc[t].insert(ch[t][u].back());
if (deg[t][u] == 1) {
for (int v: adj[t][u]) if (deg[t][v]) { fa[t][u] = v; break; }
assert(fa[t][u]);
ch[t][fa[t][u]].push_front(u);
if (ch[t][fa[t][u]].size() == 1) lbuc[t].insert(u);
}
};
update(0, fa[0][u0]), update(0, fa[0][v0]);
update(1, fa[1][u1]), update(1, fa[1][v1]); ref[u0] = ref[v0] = -1, rsv[u1] = rsv[v1] = true;
// printf("(%d,%d) & (%d,%d)\n", u0, v0, u1, v1);
solve(rest - 2);
ref[u0] = u1, ref[v0] = v1;
if (!check(u0) || !check(v0)) ref[u0] = v1, ref[v0] = u1;
} else if (deg[0][rt0] == rest - 2) { // rt0 is 1-star.
// assert(lbuc[0].size() == 2);
int x = *lbuc[0].begin(), y = *lbuc[1].begin();
if (fa[0][x] == rt0) x = *lbuc[0].rbegin();
rsv[ref[rt0] = y] = rsv[ref[x] = fa[1][y]] = true; int s = fa[0][x];
rep (i, 1, n) if (!rsv[i]) {
rsv[ref[s] = i] = true;
if (check(s)) break;
ref[s] = rsv[i] = false;
}
assert(ref[s]); for (int i = 1, j = 1; i <= n; ++i) if (!ref[i]) {
while (rsv[j]) ++j;
rsv[ref[i] = j++] = true;
}
} else { // rt1 is 1-star.
// assert(lbuc[1].size() == 2);
int x = *lbuc[1].begin(), y = *lbuc[0].begin();
if (fa[1][x] == rt1) x = *lbuc[1].rbegin();
rsv[ref[y] = rt1] = rsv[ref[fa[0][y]] = x] = true; int s = fa[1][x];
rep (i, 1, n) if (!ref[i]) {
rsv[ref[i] = s] = true;
if (check(i)) break;
ref[i] = rsv[s] = false;
}
assert(rsv[s]); for (int i = 1, j = 1; i <= n; ++i) if (!rsv[i]) {
while (ref[j]) ++j;
rsv[ref[j++] = i] = true;
}
}
} int main() {
scanf("%d", &n);
rep (t, 0, 1) rep (i, 2, n) {
int u, v; scanf("%d %d", &u, &v);
if (u > v) u ^= v ^= u ^= v;
u -= t * n, v -= t * n, ++deg[t][u], ++deg[t][v];
edge[t].insert({ u, v });
adj[t][u].push_back(v), adj[t][v].push_back(u);
} rep (i, 1, n) {
mdeg[0].insert({ deg[0][i], i });
mdeg[1].insert({ deg[1][i], i });
if (deg[0][i] == n - 1 || deg[1][i] == n - 1) return puts("No"), 0;
} init(0, 1, 0), init(1, 1, 0);
rep (t, 0, 1) rep (i, 1, n) {
if (ch[t][i].size()) {
lbuc[t].insert(ch[t][i].back());
}
} solve(n);
puts("Yes");
rep (i, 1, n) printf("%d%c", ref[i] + n, i < n ? ' ' : '\n');
return 0;
}

Solution -「CF 923F」Public Service的更多相关文章

  1. Solution -「CF 1342E」Placing Rooks

    \(\mathcal{Description}\)   Link.   在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...

  2. Solution -「CF 1622F」Quadratic Set

    \(\mathscr{Description}\)   Link.   求 \(S\subseteq\{1,2,\dots,n\}\),使得 \(\prod_{i\in S}i\) 是完全平方数,并最 ...

  3. Solution -「CF 923E」Perpetual Subtraction

    \(\mathcal{Description}\)   Link.   有一个整数 \(x\in[0,n]\),初始时以 \(p_i\) 的概率取值 \(i\).进行 \(m\) 轮变换,每次均匀随机 ...

  4. Solution -「CF 1586F」Defender of Childhood Dreams

    \(\mathcal{Description}\)   Link.   定义有向图 \(G=(V,E)\),\(|V|=n\),\(\lang u,v\rang \in E \Leftrightarr ...

  5. Solution -「CF 1237E」Balanced Binary Search Trees

    \(\mathcal{Description}\)   Link.   定义棵点权为 \(1\sim n\) 的二叉搜索树 \(T\) 是 好树,当且仅当: 除去最深的所有叶子后,\(T\) 是满的: ...

  6. Solution -「CF 623E」Transforming Sequence

    题目 题意简述   link.   有一个 \(n\) 个元素的集合,你需要进行 \(m\) 次操作.每次操作选择集合的一个非空子集,要求该集合不是已选集合的并的子集.求操作的方案数,对 \(10^9 ...

  7. Solution -「CF 1023F」Mobile Phone Network

    \(\mathcal{Description}\)   Link.   有一个 \(n\) 个结点的图,并给定 \(m_1\) 条无向带权黑边,\(m_2\) 条无向无权白边.你需要为每条白边指定边权 ...

  8. Solution -「CF 599E」Sandy and Nuts

    \(\mathcal{Description}\)   Link.   指定一棵大小为 \(n\),以 \(1\) 为根的有根树的 \(m\) 对邻接关系与 \(q\) 组 \(\text{LCA}\ ...

  9. Solution -「CF 487E」Tourists

    \(\mathcal{Description}\)   Link.   维护一个 \(n\) 个点 \(m\) 条边的简单无向连通图,点有点权.\(q\) 次操作: 修改单点点权. 询问两点所有可能路 ...

随机推荐

  1. java 关于 重写、覆写、覆盖、重载 的总结【不想再傻傻分不清了】

    1.前言 有些东西,名称不同,其实就是一个东西,你说是扯淡不? 2.重写 重写,又叫覆写.覆盖 ,注解@Override,词义为推翻 , 用法特点是继承父类后,重写的父类方法名字.参数.返回值必须相同 ...

  2. 新建koa2项目

    1.npm install -g koa-generator 2.koa2 项目名称,如果需要ejs引擎koa2 -e 项目名称 3.cd 项目名称 4.npm install 5.npm insta ...

  3. Vue系列教程(三)之vue-cli脚手架的使用

    一.Vue-cli的环境准备 目的:(1)快速管理依赖 (2)确定项目结构 1.安装node.js Node.js是一个可以让前端运行在服务器上的一个工. 下载:https://nodejs.org/ ...

  4. maven仓库策略

    当构建Maven项目时,首先检查pom.xml文件以确定依赖包的下载位置,执行顺序如下: 1.从本地资源库中查找并获得依赖包,如果没有,执行第2步. 2.从Maven默认中央仓库中查找并获得依赖包(h ...

  5. [BJDCTF2020]EzPHP-POP链

    那次某信内部比赛中有道pop链问题的题目,我当时没有做出来,所以在此总结一下,本次以buu上复现的[MRCTF2020]Ezpop为例. 题目 1 Welcome to index.php 2 < ...

  6. php中使用CURL之php curl详解

    curl是个什么东西?简单地说就是,curl是一个库,能让你通过URL和许多不同种的服务器进行勾搭.搭讪和深入交流,并且还支持许多协议.并且人家还说了curl可以支持https认证.http post ...

  7. LabVIEW生成.NET的DLL——C#下调用NI数据采集设备功能的一种方法 [原创www.cnblogs.com/helesheng]

    LabVIEW是NI公司的数据采集设备的标准平台,在其上调用NI-DAQmx驱动和接口函数能够高效的开发数据采集和控制程序.但作为一种图形化的开发语言,使用LabVIEW开发涉及算法和流程控制的大型应 ...

  8. 万字总结Keras深度学习中文文本分类

    摘要:文章将详细讲解Keras实现经典的深度学习文本分类算法,包括LSTM.BiLSTM.BiLSTM+Attention和CNN.TextCNN. 本文分享自华为云社区<Keras深度学习中文 ...

  9. 单篇长文TestNG从入门到精通

    简介 TestNG是Test Next Generation的缩写,它的灵感来自于JUnit和NUnit,在它们基础上增加了很多很牛的功能,比如说: 注解. 多线程,比如所有方法都在各自线程中,一个测 ...

  10. 18个示例详解 Spring 事务传播机制(附测试源码)

    什么是事务传播机制 事务的传播机制,顾名思义就是多个事务方法之间调用,事务如何在这些方法之间传播. 举个例子,方法 A 是一个事务的方法,方法 A 执行的时候调用了方法 B,此时方法 B 有无事务以及 ...