LG-P3549 [POI2013]MUL-Multidrink 题解
LG-P3549 [POI2013]MUL-Multidrink Solution
更好的阅读体验戳此进入
(建议您从上方链接进入我的个人网站查看此 Blog,在 Luogu 中图片会被墙掉,部分 Markdown 也会失效)
题面
一道论文题。
给定一棵有 $ n $ 个节点的树,令起点为 $ 1 $,终点为 $ n $,每步可以走的距离为 $ 1 $ 或 $ 2 $,要求不重复地遍历 $ n $ 个节点。
如果无解输出 BARK
,否则输出 $ n $ 行,表示遍历的路径,存在 SPJ。
$ 1 \le n \le 5 \times 10^5 $。
输入格式
$ n $
Describe the tree($ n - 1 $ lines in total).
图例
Solution
本题大多数内容均参考自论文《Hamiltonian paths in the square of a tree》。
作者:Jakub Radoszewski and Wojciech Rytter。
原文链接:Hamiltonian paths in the square of a tree
首先我们引入一个定义:caterpillar(毛毛虫),原文定义为 a single path with several leafs connected to it. 简而言之就是其为一棵树,一棵树为 caterpillar 当且仅当去掉其所有叶子节点后剩下的仅为一条单链,如图即为一个 caterpillar。
对于其更多的性质及求法,可以去做一下 LG-P3174 [HAOI2009] 毛毛虫,但与本题关联不大,就不再赘述。
现在我们引入下一个定义,对于图 $ G = (V, E) $,我们定义 $ G $ 的 $ k $ 次幂,记作 $ G^k $,为一个基于(或者说完全覆盖,原文为 cover)点集 $ V $ 的图,对于 $ \forall u, v \in V \(,\) u, v $ 之间有边相连当且仅当在 $ G $ 中 $ u, v $ 之间的路径长度不大于 $ k $。我们称 $ G^2 $ 为 the square of $ G $,称 $ G^3 $ 为 the cube of $ G $。
然后我们再引入一个定义:2-connected graph(2-连通图),这个定义类似点双,对于具有割点的图我们定义其为 1-connected graph,则同理 2-connected graph 只得就是至少需要去掉两个顶点才能使图的连通分支增加。
现在我们再引入几个定理:
Lemma 1:对于树 $ T $,图 $ T^2 $ 包含哈密尔顿回路当且仅当 $ T $ 为一个 caterpillar。
Lemma 2:对于树 $ T $,图 $ T^2 $ 包含哈密尔顿路径当且仅当 $ T $ 为一个 horsetail。
证明:咕咕咕。
然后我们继续进行定义。
定义 $ P $ 为 $ G $ 的 k-path 当且仅当 $ P $ 为 $ G^k $ 中的一条路径,同理我们定义 k-cycle。
额外地,我们分别定义 $ G $ 的 kH-path 和 kH-cycle 为 $ G^k $ 中的哈密尔顿路径和哈密尔顿回路。
若 $ G^k $ 包含一个 kH-cycle,我们称 $ G $ 为 k-Hamiltonian(这玩意我也不知道怎么译),同理包含 kH-path 称为 k-traceable。
然后对于 caterpillar,我们额外定义其为 non-trivial 当且仅当其至少含有 $ 1 $ 条边。
我们称 caterpillar 的主链,也就是去掉叶子结点剩下的链,为 spine。
下面开始正文:
我们考虑将原图中 $ 1 $ 到 $ n $ 的路径抽离出来,作为其主干,然后将主干上每个点及其每个子树作为一个新的部分,这里有一个性质,对于这些非主干的部分,有一个性质:若其非 caterpillar,则一定无解,这个东西自己画一下找找性质就会发现很显然,当然严谨的证明我不会。
然后这里我们对于每个主干上的点划分为两类:
- Type A:有不超过 $ 1 $ 个 non-trivial caterpillar。
- Type B:有且仅有 $ 2 $ 个 non-trivial caterpillar。
不难发现,对于 Type A 的节点,若从主干上的节点进入,最后一步一定刚好走到下一个主干节点上,对于 Type B 的节点,在进入时必须直接进入到其任意子树中,否则无解,对于后面的无解判断我们主要也就靠这个性质。
这里有一个性质:若一个点有超过两个的 non-trivial caterpillar 则一定无解,这个依然,自己画一下会发现无论上一步刚好走到这个点还是直接走到某一个子树上,最终都是无解的,路径一定会断在某个子树中,同样严谨的证明我不会。
然后我们额外地定义一个点为 free 当且仅当其为单点,即没有任何子树的主干节点。
如下图例:
下一个性质:每个 Type B 的节点前后都必须有至少一个 free 节点,否则无解。显然 free 点是可以将进入下一个主干节点和进入下一个主干节点的某一个子节点相互转换的,而 Type A 不能进行这样的转换,两棵 Type B 之间又需要有这样的转换。
对于满足以上所有性质的主干,我们称其为 $ (u, v) $ -horsetail,同时称这整棵树为 horsetail。
现在我们对上面的性质做一个总结:
- 每一个主干节点都为 Type A 或 Type B。
- 在任意两个 Type B 之间都至少有一个 free。
- 在 Type B 之前至少有一个 free。
- 在 Type B 之后至少有一个 free。
- 至少有一个 free。
显然对于上面的四个图例中,(a)(b) 为 horsetail,有解,(c)(d) 非 horsetail,无解。
而对于我们本题的图 $ G $,我们也就需要先判断其是否为 horsetail,如果不是则无解,如果是那么说明 $ G^2 $,也就是按照本题要求构成的图,一定存在一条哈密顿路径,即 Lemma 2。
现在我们对 Lemma 1 进行进一步的阐释:
Lemma 1:任意一个在 caterpillar 上的 2H-cycle 都一定有以下的顺序:我们定义 caterpillar 的 spine 长度为 $ l $,令 spine 为 $ v_1, v_2, \cdots, v_l $,spine 上的叶子为 $ P_1, P_2, \cdots, P_l $,这里我们定义 $ P_i $ 表示一个任意的 $ v_i $ 的叶子的排列。
如此图中,显然 $ 1, 2, 3, \cdots, 8, 1 $ 和 $ 1, 2, 3, \cdots, 10, 1 $ 分别为两图的一种 2H-cycle。
所以当我们想要对 horsetail 上的 caterpillar 找到 2H-path 的时候,只需要取 2H-cycle 中的一部分,或者说断开 2H-cycle 中的一条边,如 $ 2, 3, 4, \cdots, 8, 1 $ 这种顺序。
然后再次回到本题,或者说回到 Lemma 2:
对于一个 2H-path $ S $,当树 $ T $ 上的节点被按照分别遍历每个主干节点的每个子树的顺序遍历时,同时我们定义每个主干节点 $ u_i $ 所在子树节点集合为 $ layer(u_i) $,满足每个 $ layer(u_i) $ 都按照特定顺序遍历,我们称树 $ T $ 为 layered,此时我们便可以根据每个主干节点的路径将 $ S $ 分成 $ S_1, S_2, S_3, \cdots $。我们定义一个路径 $ S $ 的起点和终点分别为 $ first(S) $ 和 $ last(S) $,定义主干节点为 main,主干节点直接连接的非主干节点为 secondary,则显然对于每个 $ i \(,\) first(S_i) $ 和 $ last(S_i) $ 均为 main 或 secondary。
对于我们的算法我们首先需要实现一些辅助函数:(为了便于理解,部分函数名与原论文中有微调)
vector < int > BuildCaterpillar(int mp, int S, int T)
:
对于从 $ S $ 开始 $ T $ 结束的,挂在 horsetail 的主干上的 $ 1 $ 或 $ 2 $ 条 caterpillar 生成 2H-path,这里我们首先需要对这一部分构建一个新的图,将 caterpillar 上所有叶子指向 spine,同时我们需要维护每条 caterpillar 的主干,具体通过代码中的 void BuildSpine(int fa, int p)
实现,我们称 caterpillar 所在的 horsetail 上的主干节点为 $ mp $,那么当 $ mp $ 为 type B 的时候,也就是 non-trivial 的 caterpillar 有两条的时候,我们便可以将前面一条 caterpillar 反过来与其首尾相接,当成同一条 caterpillar,这样最终我们对于形成的 caterpillar 进行如 Lemma 1 地生成 2H-cycle,然后断开其中 $ S \rightarrow T $ 的边(或 $ T \rightarrow S $)即可,同时需要注意如果起始点是在非 non-trivial 的 secondary 节点上,那么我们就需要先遍历其叶子节点,然后最后再遍历其 spine,对于遍历 $ mp $ 的叶子节点,我们通过 void extend(int mp, int unreach1 = -1, int unreach2 = -1)
实现,具体可以看一下代码。
int FindAnySecondaryNode(int p)
:找到 horsetail 中主干为 $ p $ 的任意一个 secondary 节点。
int FindAnySecondaryNode_PreferablyLeaf(int p)
:找到 horsetail 中主干为 $ p $ 的任意一个 secondary 节点,且优先找非 non-trivial 的叶子节点。
int FindAnotherSecondaryNode(int p, int lst)
:找到 horsetail 中主干为 $ p $ 的非 $ lst $ 的另一个 secondary 节点。
然后对于具体的实现,首先我们需要检查一下其是否为无解,也就是前面的检查是否为 horsetail,具体由 void Check(void)
实现。
然后我们算法的核心就是找到 horsetail 的那一条 2H-path,通过 vector < int > Get2HPathHorsetail(void)
实现,大概的思路就是先对第一个节点进行讨论,显然起点一定为 $ 1 $,如果其为 free 节点那么终点也为 $ 1 $,否则一定为其的某一个 secondary 节点,然后构造此 2H-path,对于后面的每个点,如果是 free 节点那么起点终点均为主干点 $ mp $,否则如果上次的终点不是主干节点,这次的起点一定为 $ mp $,终点为优先找非 non-trivial 节点的任意一个 secondary 节点,如果上次的终点是主干节点,那么这次的起点一定为优先找非 non-trivial 节点的 secondary 节点,对于终点,如果该节点是 type B,那么终点也需要为 secondary 节点,找到另外的一个 secondary 节点即可,否则终点为 $ mp $ 即可。
大致的思路就是这样,码量大概 7.5KiB
,随后的复杂度是 linear 的,也就是 $ O(n) $ 的。
同时这里提供一个原论文中的对于最后的 Get2HPathHorsetail(void)
的实现,可以选择性地看一下。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
mp => mainp
subt => subtree
fa => father
fst => first
lst => last
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define EXIT puts("BRAK"), exit(0)
#define MAXN 510000
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[(MAXN << 1) + MAXN];
ROPNEW(ed);
Edge* head[MAXN];
int N;
int mainLen(0);
int fa[MAXN], deg[MAXN];
int mainp[MAXN];
int non_trivial[MAXN];
bool isMainp[MAXN], isFree[MAXN];
void dfs(int p = 1){
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa[p])
fa[SON] = p,
dfs(SON);
}
void InitMainp(void){
int cur = N;
do{
mainp[++mainLen] = cur;
isMainp[cur] = true;
cur = fa[cur];
}while(cur != 1);
mainp[++mainLen] = 1;
isMainp[1] = true;
reverse(mainp + 1, mainp + mainLen + 1);
}
bool isCaterpillar(int fa, int p){
int cnt(0);
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa || deg[SON] == 1)continue;
if(!isCaterpillar(p, SON))return false;
if(cnt++)return false;
}return true;
}
void Check(void){
for(int p = 1; p <= mainLen; ++p){
int mp = mainp[p];
isFree[mp] = true;
for(auto i = head[mp]; i; i = i->nxt){
if(isMainp[SON])continue;
isFree[mp] = false;
if(deg[SON] == 1)continue;
++non_trivial[mp];
if(non_trivial[mp] > 2)EXIT;
if(!isCaterpillar(mp, SON))EXIT;
}
}
int curFree(0);
bool end_with_B(true);
bool exist_free(false);
for(int p = 1; p <= mainLen; ++p){
int mp = mainp[p];
if(isFree[mp]){++curFree; exist_free = true; end_with_B = false; continue;}
if(non_trivial[mp] == 2){
if(!curFree)EXIT;
curFree = 0;
end_with_B = true;
}
}
if(end_with_B || !exist_free)EXIT;
}
int FindAnySecondaryNode(int p){
for(auto i = head[p]; i; i = i->nxt)
if(!isMainp[SON])return SON;
return -1;
}
int FindAnySecondaryNode_PreferablyLeaf(int p){
for(auto i = head[p]; i; i = i->nxt)
if(!isMainp[SON] && deg[SON] == 1)return SON;
return FindAnySecondaryNode(p);
}
int FindAnotherSecondaryNode(int p, int lst){
for(auto i = head[p]; i; i = i->nxt)
if(!isMainp[SON] && SON != lst)return SON;
return -1;
}
namespace Caterpillar{
vector < int > route;
Edge* head[MAXN];
vector < int > spine;
enum type{spineNode = 1, leafNode};
int ffa[MAXN];
void add(int s, int t){
head[s] = new Edge{head[s], t};
ffa[t] = s;
}
void BuildSpine(int fa, int p){
spine.push_back(p);
for(auto i = ::head[p]; i; i = i->nxt){
if(SON == fa)continue;
if(::deg[SON] == 1)add(p, SON);
else BuildSpine(p, SON);
}
}
void extend(int mp, int unreach1 = -1, int unreach2 = -1){
for(auto i = head[mp]; i; i = i->nxt){
if(SON == unreach1 || SON == unreach2)continue;
route.push_back(SON);
}
}
vector < int > BuildCaterpillar(int mp, int S, int T){
route.clear();
spine.clear();
route.push_back(S);
if(S == T)return route;
spine.push_back(mp);
bool exist_caterpillar(false);
for(auto i = ::head[mp]; i; i = i->nxt){
if(isMainp[SON])continue;
if(deg[SON] == 1)add(mp, SON);
else{
if(!exist_caterpillar)exist_caterpillar = true;
else reverse(spine.begin(), spine.end());
BuildSpine(mp, SON);
}
}
vector < pair < int, type >/*spine_node_pos, spine or leaf*/ > temp;
vector < pair < int, type >/*spine_node_pos, spine or leaf*/ > unextended;
for(int i = 0; i < (int)spine.size(); ++i)
temp.push_back({spine.at(i), !(i & 1) ? spineNode : leafNode});
for(int i = (int)spine.size() - 1; i >= 0; --i)
temp.push_back({spine.at(i), (i & 1) ? spineNode : leafNode});
for(auto it = temp.begin(); it < temp.end(); ++it)
if(it->second == spineNode || head[it->first])unextended.push_back(*it);
#define LEFT(x) (x == 0 ? (int)unextended.size() - 1 : x - 1)
#define RIGHT(x) (x == (int)unextended.size() - 1 ? 0 : x + 1)
auto Beg = deg[S] == 1 ? make_pair(ffa[S], leafNode) : make_pair(S, spineNode);
auto End = deg[T] == 1 ? make_pair(ffa[T], leafNode) : make_pair(T, spineNode);
int begPos = -1; while(unextended.at(++begPos) != Beg);
if(Beg.second == leafNode)extend(Beg.first, S, T);
if(unextended.at(LEFT(begPos)) == End)
for(int j = RIGHT(begPos); unextended.at(j) != End; j = RIGHT(j))
unextended.at(j).second == spineNode
? route.push_back(unextended.at(j).first)
: extend(unextended.at(j).first);
else
for(int j = LEFT(begPos); unextended.at(j) != End; j = LEFT(j))
unextended.at(j).second == spineNode
? route.push_back(unextended.at(j).first)
: extend(unextended.at(j).first);
if(End.second == leafNode && Beg != End)extend(End.first, S, T);
route.push_back(T);
return route;
}
}
vector < int > Get2HPathHorsetail(void){
vector < int > ret;
int fst = mainp[1];
int lst = isFree[mainp[1]]
? mainp[1]
: FindAnySecondaryNode(mainp[1]);
auto tmp = Caterpillar::BuildCaterpillar(mainp[1], fst, lst);
ret.insert(ret.end(), tmp.begin(), tmp.end());
for(int i = 2; i <= mainLen; ++i){
int w = mainp[i];
if(isFree[w])fst = lst = w;
else if(!isMainp[lst])
fst = w,
lst = FindAnySecondaryNode_PreferablyLeaf(w);
else
fst = FindAnySecondaryNode_PreferablyLeaf(w),
lst = non_trivial[w] == 2
? FindAnotherSecondaryNode(w, fst)
: w;
auto cp = Caterpillar::BuildCaterpillar(w, fst, lst);
ret.insert(ret.end(), cp.begin(), cp.end());
}
return ret;
}
int main(){
N = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
++deg[s], ++deg[t];
}
dfs();
InitMainp();
Check();
auto ans = Get2HPathHorsetail();
for(auto i : ans)printf("%d\n", i);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
写在后面
就是这道不怎么友好的题,我做的时候网上几乎没有题解,只能找到几篇说的很简略的题解,然后没办法只能去自己翻英文论文,然后一个一个查词,在我快要写完这道题的时候,才突然发现 Luogu 上多出来了两篇题解。。
然后就是调这道题,一共交了 $ 70+ $ 次才过了这道题,自己写完之后怎么改都是 $ 70 $ pts,Luogu 上下不了数据,hydro 等没有 SPJ,没办法只能开始把每一部分的代码对着大佬的代码改,最后几乎把整篇代码都重构了一遍才找到错误,只是调代码就调了一天半,不过至少最后还是过了。
UPD
update-2022_10_09 初稿
update-2022_10_10 修复一些错误