(重发下这篇原发于 2014-03-25 的博客,原系列的其他三篇博客被网易莫名禁掉了。。。所以把那三篇连同最后这篇一起搬过来)
link-cut cactus
首先我们回忆一下之前的做法——维护仙人掌的一棵生成树,非树边作为原子信息出现。
然后我们维护生成树的方式是用lct。也就是说我们维护一棵树的链剖分。
啊哈!那么我们为什么不能直接维护仙人掌的链剖分?
这样我们就得到了link-cut cactus。(……这样命名应该没问题?)
类比link-cut tree,我们研究一下核心操作access。
当然我们还是像lct一样要找个点当根。
我们规定access(x)就会把x到根的最短路全变为实边。
如果实现了这样一个基础操作,那么很多事情就非常简洁了。最短路查询什么的,换根什么的,打标什么的,想怎么做就怎么做。
那么首先面临的一个难题是,什么是实边,什么是虚边?
对于不在环上的部分,显然延续原来的lct的定义是没问题的。
但是如果有环就没“父亲”这一概念,显得很棘手。
没办法,那么我们就以环为单位进行定义吧。
没有环的话就是普通情况,与lct一样,结点有个preferred child,与之相连的边是实边。
有环的话就是
文艺情况。首先由于我们是有根的仙人掌,那么环上肯定有一个离根最近的点,我们称为这个环的根。即,从仙人掌的根往下走碰到的第一个在这个环上的点。
对于一个环它有一个preferred child,从环的根到这个结点的最短路径(如果有多条选任意一条)上的边都是实边。那么还剩下另一半的环,我们称为这个环的额外链。额外链的两端是虚边,其它边均是实边。
下图是一个例子。
注意下面三种情况是不被允许的:
那么我们对于每个环记录一些信息来更方便地操作它。
记录pA为环的根,pB为环的preferred child,pEx是环的额外链的splay树的根结点。
而那两条环上的黑边会作为额外链的firstE和lastE被保存下来。
不过这就让我们不得不注意边界情况:
对于这种情况是没有额外链的,自然地pEx也就为空,这样那条黑边就没人保存了。
我没有想到什么简洁优美和谐统一的方式解决这个问题,于是只好多开个missingE来记录这条边。
那么我们还有没有漏掉什么神奇的地方?有!
换根……
回忆lct的换根:
但是我们考虑一棵仙人掌的换根:
于是就悲剧了。
难道说不能换根了?
不,可以换。我们注意到不仅是pEx的顺序变了,连pA和pB也要受影响。不过只要把pA和pB对调就可以了。
接着我们发现,可以在splay中比较pA和pB的先后顺序,如果反了就把环信息中pA和pB指针对调,并且给pEx打上翻转标记即可。
这样就讨论齐全了。
在access的时候,如果发现了环,我们就先调整pA、pB使得它们顺序正确。
后面的事情就简单了:
下面给出伪代码。……由于要判断pA和pB的先后顺序……而且还有2B情况和pEx为空的情况过来砸场子……
显得很麻烦的样子 = =
void access(x) { for (p = x, q = NULL; p; q = p, p = p->fa) { splay(p); if (p->prevE && p->prevE->cir) // 判断p是否在环上。注意环的根不算作在这个环上。 { isTogether = false; // 判断是否是2B情况。 cir = p->prevE->cir; // 获取p->prevE所在的环的信息 // 由于p可能在额外链上而之前很狗血地splay了,会导致记录的pEx不正确。 if (cir->pEx && !cir->pEx->isRoot()) cir->pEx = p; splay(cir->pB); splay(cir->pA); if (cir->pB->isRoot()) // 2B情况 { if (cir->pB->fa != cir->pA) // 如果pA、pB顺序不对则进行调整 { swap(cir->pA, cir->pB); if (cir->pEx) cir->pEx->tag_rev(); // 打上翻转标记 } } else // 文艺情况 { isTogether = true; splay_until(cir->pB, cir->pA); // 把pB splay到pA下面 if (cir->pA->lc == cir->pB) // 如果pA、pB顺序不对则进行调整 { rotate(cir->pB); // 一次旋转把pB转成根 swap(cir->pA, cir->pB); if (cir->pEx) cir->pEx->tag_rev(); // 打上翻转标记 } cir->pA->rc = NULL; cir->pA->nextE = NULL; // 暂时断开pA与下面部分的链接转化为2B情况 } cir->pB->rc = cir->pEx; // pEx为空的情况,用missingE补上 cir->pB->nextE = cir->pEx ? cir->pEx->msg.firstE : cir->missingE; if (cir->pEx) cir->pEx->fa = cir->pB; // 这样环就被整个地接了起来成为了一棵splay。 p->splay(); // 比较哪边走比较短,如果不是往左走短就调整一下 if (p->getLcTotL() > p->getRcTotL()) { p->tag_rev(); p->tag_down(); } cir->pB = p; cir->pEx = p->rc; // 把较长的那条变为额外链 cir->missingE = p->rc ? NULL : p->nextE; // pEx为空的情况,用missingE补上 if (cir->pEx) cir->pEx->fa = NULL; p->rc = q; p->nextE = q ? q->msg.firstE : NULL; p->update(); if (isTogether) // 如果是文艺情况还得把pA接回来 { cir->pA->rc = p; cir->pA->nextE = p->msg.firstE; p->splay(); } } else // 普通情况 { p->rc = q; p->nextE = q ? q->msg.firstE : NULL; p->update(); } } }
至于多条最短路的情况,可以在环信息里记录pA到pB是否有两条最短路,在access的时维护下。这样在统计信息的时候考虑下就好了。
时间复杂度分析?
不会证splay和lct的时间复杂度请回去补……
显然结点和环的preferred child的切换次数是均摊O(log n)的。这样我们就有access的上界O(log^2n)
但是貌似没办法再使用lct的势能分析了。所以最坏情况也应该是均摊O(log^2n)了。虽然我一时想到什么很好的例子卡到O(log^2n),但是看在这么多次splay的份上不是平方就怪了……
写起来还是比维护生成树法爽多了。
实际效率的话……比维护生成树法略慢一些。还算比较快吧,缩小点数据范围的话估计看不出来了。
下面我把动态仙人掌III的link-cut cactus版贴出来吧。
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <cassert> #include <climits> using namespace std; const int INF = INT_MAX; const int MaxN = 100000; inline int getint() { char c; while (c = getchar(), ('0' > c || c > '9') && c != '-'); if (c != '-') { int res = c - '0'; while (c = getchar(), '0' <= c && c <= '9') res = res * 10 + c - '0'; return res; } else { int res = 0; while (c = getchar(), '0' <= c && c <= '9') res = res * 10 + c - '0'; return -res; } } template <class T> class BlockAllocator { private: static const int BlockL = 10000; union TItem { char rt[sizeof(T)]; TItem *next; }; TItem *pool, *tail; TItem *unused; public: BlockAllocator() { pool = NULL; unused = NULL; } T *allocate() { TItem *p; if (unused) { p = unused; unused = unused->next; } else { if (pool == NULL) pool = new TItem[BlockL], tail = pool; p = tail++; if (tail == pool + BlockL) pool = NULL; } return (T*)p; } void deallocate(T *pt) { TItem *p = (TItem*)pt; p->next = unused, unused = p; } }; struct edgeWeight; struct path_message; struct lcc_circle; struct lcc_edge; struct lcc_message; struct lcc_node; struct edgeWeight { int wA, wB; edgeWeight(){} edgeWeight(const int &_wA, const int &_wB) : wA(_wA), wB(_wB){} friend inline bool operator==(const edgeWeight &lhs, const edgeWeight &rhs) { return lhs.wA == rhs.wA && lhs.wB == rhs.wB; } friend inline bool operator!=(const edgeWeight &lhs, const edgeWeight &rhs) { return lhs.wA != rhs.wA || lhs.wB != rhs.wB; } }; struct path_message { int minLA; int minWB; path_message(){} path_message(const edgeWeight &ew) : minLA(ew.wA), minWB(ew.wB){} path_message(const int &_minLA, const int &_minWB) : minLA(_minLA), minWB(_minWB){} void setEmpty() { minLA = 0; minWB = INF; } void setInvalid() { minLA = -1; minWB = -1; } bool valid() const { return minLA != -1; } void setMultiple() { minWB = -1; } friend inline path_message operator+(const path_message &lhs, const path_message &rhs) { if (lhs.minLA < rhs.minLA) return lhs; else if (rhs.minLA < lhs.minLA) return rhs; else return path_message(lhs.minLA, -1); } friend inline path_message operator*(const path_message &lhs, const path_message &rhs) { return path_message(lhs.minLA + rhs.minLA, min(lhs.minWB, rhs.minWB)); } }; struct lcc_circle { lcc_node *pA, *pB; lcc_node *pEx; lcc_edge *missingE; bool equalL; }; struct lcc_edge { edgeWeight ew; lcc_circle *cir; inline lcc_circle *getCir() { return this ? this->cir : NULL; } }; struct lcc_message { path_message pathMsg; lcc_edge *firstE, *lastE; bool hasCir; bool hasMultiplePath; void rev() { swap(firstE, lastE); } void coverCir(lcc_circle *cir, bool isSingle) { hasCir = !isSingle && cir != NULL; hasMultiplePath = false; if (cir && firstE->getCir() != cir && lastE->getCir() != cir) { if (cir->equalL) hasMultiplePath = true; } } void addWB(int delta, bool isSingle) { if (!isSingle) pathMsg.minWB += delta; } friend inline lcc_message operator+(const lcc_message &lhs, const lcc_message &rhs) { lcc_message res; assert(lhs.lastE == rhs.firstE); lcc_edge *e = lhs.lastE; res.pathMsg = lhs.pathMsg * path_message(e->ew) * rhs.pathMsg; res.hasMultiplePath = lhs.hasMultiplePath || rhs.hasMultiplePath; if (e->cir && lhs.firstE->getCir() != e->cir && rhs.lastE->getCir() != e->cir) { if (e->cir->equalL) res.hasMultiplePath = true; } res.firstE = lhs.firstE, res.lastE = rhs.lastE; res.hasCir = lhs.hasCir || e->cir || rhs.hasCir; return res; } }; struct lcc_node { lcc_node *fa, *lc, *rc; lcc_edge *prevE, *nextE; lcc_message msg; bool hasRev; bool hasCoveredCir; lcc_circle *coveredCir; int wBDelta; bool isRoot() { return !fa || (fa->lc != this && fa->rc != this); } void rotate() { lcc_node *x = this, *y = x->fa, *z = y->fa; lcc_node *b = x == y->lc ? x->rc : x->lc; x->fa = z, y->fa = x; if (b) b->fa = y; if (z) { if (z->lc == y) z->lc = x; else if (z->rc == y) z->rc = x; } if (y->lc == x) x->rc = y, y->lc = b; else x->lc = y, y->rc = b; y->update(); } void allFaTagDown() { int anc_n = 0; static lcc_node *anc[MaxN]; anc[anc_n++] = this; for (int i = 0; !anc[i]->isRoot(); i++) anc[anc_n++] = anc[i]->fa; for (int i = anc_n - 1; i >= 0; i--) anc[i]->tag_down(); } void splay() { allFaTagDown(); while (!this->isRoot()) { if (!fa->isRoot()) { if ((fa->lc == this) == (fa->fa->lc == fa)) fa->rotate(); else this->rotate(); } this->rotate(); } this->update(); } void splay_until(lcc_node *target) { allFaTagDown(); while (this->fa != target) { if (fa->fa != target) { if ((fa->lc == this) == (fa->fa->lc == fa)) fa->rotate(); else this->rotate(); } this->rotate(); } this->update(); } int getLcTotL() { if (!prevE) return 0; int totL = prevE->ew.wA; if (lc) totL += lc->msg.pathMsg.minLA + msg.firstE->ew.wA; return totL; } int getRcTotL() { if (!nextE) return 0; int totL = nextE->ew.wA; if (rc) totL += rc->msg.pathMsg.minLA + msg.lastE->ew.wA; return totL; } void access() { for (lcc_node *p = this, *q = NULL; p; q = p, p = p->fa) { p->splay(); if (p->prevE && p->prevE->cir) { bool isTogether = false; lcc_circle *cir = p->prevE->cir; if (cir->pEx && !cir->pEx->isRoot()) cir->pEx = p; cir->pB->splay(), cir->pA->splay(); if (cir->pB->isRoot()) { if (cir->pB->fa != cir->pA) { swap(cir->pA, cir->pB); if (cir->pEx) cir->pEx->tag_rev(); } } else { isTogether = true; cir->pB->splay_until(cir->pA); if (cir->pA->lc == cir->pB) { cir->pB->rotate(); swap(cir->pA, cir->pB); if (cir->pEx) cir->pEx->tag_rev(); } cir->pA->rc = NULL, cir->pA->nextE = NULL; } cir->pB->rc = cir->pEx, cir->pB->nextE = cir->pEx ? cir->pEx->msg.firstE : cir->missingE; if (cir->pEx) cir->pEx->fa = cir->pB; p->splay(); if (p->getLcTotL() > p->getRcTotL()) p->tag_rev(), p->tag_down(); cir->pB = p; cir->pEx = p->rc, cir->missingE = p->rc ? NULL : p->nextE; cir->equalL = p->getLcTotL() == p->getRcTotL(); if (cir->pEx) cir->pEx->fa = NULL; p->rc = q, p->nextE = q ? q->msg.firstE : NULL; p->update(); if (isTogether) { cir->pA->rc = p, cir->pA->nextE = p->msg.firstE; p->splay(); } } else { p->rc = q, p->nextE = q ? q->msg.firstE : NULL; p->update(); } } this->splay(); } void makeRoot() { this->access(); this->tag_rev(), this->tag_down(); } lcc_node *findRoot() { lcc_node *p = this; p->access(); while (p->tag_down(), p->lc) p = p->lc; p->splay(); return p; } void tag_rev() { hasRev = !hasRev; msg.rev(); } void tag_coverCir(lcc_circle *cir) { hasCoveredCir = true; coveredCir = cir; msg.coverCir(cir, !lc && !rc); } void tag_addWB(int delta) { wBDelta += delta; msg.addWB(delta, !lc && !rc); } void tag_down() { if (hasRev) { swap(lc, rc); swap(prevE, nextE); if (lc) lc->tag_rev(); if (rc) rc->tag_rev(); hasRev = false; } if (hasCoveredCir) { if (lc) { prevE->cir = coveredCir; lc->tag_coverCir(coveredCir); } if (rc) { nextE->cir = coveredCir; rc->tag_coverCir(coveredCir); } hasCoveredCir = false; } if (wBDelta != 0) { if (lc) { prevE->ew.wB += wBDelta; lc->tag_addWB(wBDelta); } if (rc) { nextE->ew.wB += wBDelta; rc->tag_addWB(wBDelta); } wBDelta = 0; } } void update() { msg.pathMsg.setEmpty(); msg.firstE = prevE, msg.lastE = nextE; msg.hasCir = false; msg.hasMultiplePath = false; if (lc) msg = lc->msg + msg; if (rc) msg = msg + rc->msg; } }; int n; lcc_node lccVer[MaxN + 1]; BlockAllocator<lcc_edge> lccEAllocator; BlockAllocator<lcc_circle> lccCirAllocator; void cactus_init() { for (int v = 1; v <= n; v++) { lcc_node *x = lccVer + v; x->fa = x->lc = x->rc = NULL; x->prevE = x->nextE = NULL; x->hasRev = false; x->hasCoveredCir = false; x->wBDelta = 0; x->update(); } } bool cactus_link(int v, int u, int wA, int wB) { if (v == u) return false; edgeWeight ew(wA, wB); lcc_node *x = lccVer + v, *y = lccVer + u; x->makeRoot(), y->makeRoot(); if (x->fa) { x->access(); if (x->msg.hasCir) return false; lcc_circle *cir = lccCirAllocator.allocate(); lcc_edge *e = lccEAllocator.allocate(); e->ew = ew, e->cir = cir; cir->pA = y, cir->pB = x, cir->pEx = NULL; cir->missingE = e; x->tag_coverCir(cir); x->access(); } else { lcc_edge *e = lccEAllocator.allocate(); e->ew = ew, e->cir = NULL; x->fa = y, x->prevE = e, x->update(); } return true; } bool cactus_cut(int v, int u, int wA, int wB) { if (v == u) return false; edgeWeight ew(wA, wB); lcc_node *x = lccVer + v, *y = lccVer + u; if (x->findRoot() != y->findRoot()) return false; y->makeRoot(), x->access(); y->splay_until(x); lcc_circle *cir = x->prevE->cir; if (cir && cir->pA == y && !cir->pEx && cir->missingE->ew == ew) { lcc_edge *e = cir->missingE; x->tag_coverCir(NULL); lccCirAllocator.deallocate(cir); lccEAllocator.deallocate(e); return true; } if (!y->rc && x->prevE->ew == ew) { lcc_edge *e = x->prevE; lccEAllocator.deallocate(e); if (cir) { if (cir->pEx) { cir->pEx->tag_rev(); cir->pEx->fa = y, y->rc = cir->pEx; y->nextE = cir->pEx->msg.firstE; x->prevE = cir->pEx->msg.lastE; } else y->nextE = x->prevE = cir->missingE; y->update(), x->update(); x->tag_coverCir(NULL); lccCirAllocator.deallocate(cir); } else { y->fa = NULL, y->nextE = NULL, y->update(); x->lc = NULL, x->prevE = NULL, x->update(); } return true; } return false; } bool cactus_add(int qv, int qu, int delta) { lcc_node *x = lccVer + qv, *y = lccVer + qu; if (x->findRoot() != y->findRoot()) return false; x->makeRoot(), y->access(); if (y->msg.hasMultiplePath) return false; y->tag_addWB(delta); return true; } path_message cactus_query(int qv, int qu) { path_message res; lcc_node *x = lccVer + qv, *y = lccVer + qu; if (x->findRoot() != y->findRoot()) { res.setInvalid(); return res; } x->makeRoot(), y->access(); res = y->msg.pathMsg; if (y->msg.hasMultiplePath) res.setMultiple(); return res; } int main() { int nQ; cin >> n >> nQ; cactus_init(); while (nQ--) { char type; while (type = getchar(), type != 'l' && type != 'c' && type != 'a' && type != 'd'); if (type == 'l') { int v = getint(), u = getint(), wA = getint(), wB = getint(); if (cactus_link(v, u, wA, wB)) printf("ok\n"); else printf("failed\n"); } else if (type == 'c') { int v = getint(), u = getint(), wA = getint(), wB = getint(); if (cactus_cut(v, u, wA, wB)) printf("ok\n"); else printf("failed\n"); } else if (type == 'a') { int v = getint(), u = getint(), delta = getint(); if (cactus_add(v, u, delta)) printf("ok\n"); else printf("failed\n"); } else if (type == 'd') { int v = getint(), u = getint(); path_message ret = cactus_query(v, u); printf("%d %d\n", ret.minLA, ret.minWB); } else { puts("error!"); } } return 0; }