(重发下我这篇原发于 2014-03-19 的网易博客,原博客被网易莫名禁掉了。。被迫手动搬家,忧伤)
动态仙人掌 II
由于我们知道x->msg->firstE和x->msg->lastE,以及它们的环编号,所以也就能知道环所对应的非树边。
所以看起来,原子信息是在结点上的:x->prevExMsg和x->nextExMsg。(x->prevE和x->nextE的定义如前文所述)
观察此图。哪些结点的拓展信息可能被修改了呢?
能link、cut、查询最短路信息。
信息包括长度和边权最小值。也就是说信息可合并但不可减。
如果只能合并的话,那么直截了当的方法就是让链记录这样子的信息:
也就是说我们要知道树链信息和环的一部分
信息。
我们需要进一步地明确究竟需要知道什么。
显然,splay树维护的
树链
信息是肯定要有的。不仅如此,我们还需要一个蓝尾巴一个绿尾巴。
那么尾巴是啥?注意我们维护的是仙人掌的一棵生成树,非树边是我们不方便统计的。
所以我们定义拓展信息exMsg。是指沿着环往链外走,不许走非树边,一直走到不能走的这条路径的信息。信息包括长度和权值最小值。
这样我们就清楚了我们到底要知道什么信息:
于是信息的合并就方便了。需要讨论一下
没head、
没tail
的情况。不再赘述。
于是我们就完美地O(n log n)解决了?
显然没有!
我们忽略了一个难点:原子信息。
我们甚至还没有讨论这个信息的原子信息是储存在边上还是储存在结点上!
我们来思考一下不断分割信息最后得到的不可分割的原子信息是什么。发现应该是这样的:
那么
x->prevExMsg的定义应该是:
1. 如果x->prevE不在环上,
那么没有x->prevExMsg。
2. 如果x->prevE和x->nextE在同一个环上,
那么没有x->prevExMsg。(我们认为x->prevE与NULL不在同一个环上)
3. 否则存在
x->prevExMsg,记录了从x结点出发,在x->prevE所在环上走,且不往x->prevE的方向走,不允许走非树边,一直走到不能走的这条路径的信息。
信息包括长度和权值最小值。
x->nextExMsg的定义也是类似的。
如果我们认为空边NULL的环编号为空的话,1、2可以简化为:
如果x->prevE和x->nextE的环编号相同,
那么没有x->prevExMsg。
注意这个定义其实非常优美而简洁。这意味着只要知道了树的形态,x和x->prevE、x->nextE,就能知道拓展信息。与树的链剖分无关。(当然了,x->prevE和x->nextE与树的链剖分有关)
需要强调的一点是,树的总根的prevE为NULL,所以如果nextE有环编号就一定有nextExMsg。任意一条链的底端也是同理,它的nextE为NULL,所以如果prevE有环编号就一定有prevExMsg。
这意味着我们查询时只需要:
query (x, y) { if (findRoot(x) != findRoot(y)) return NULL; makeRoot(x); access(y); splay(y); return y->msg->midMsg; }
而不必过多地纠结细节。
于是剩下的问题是,怎么维护结点的拓展信息?
如果树的形态不变、链剖分不变,那么显然结点的拓展信息不会变。
link、cut能改变树的形态,access能改变链剖分。
我们现在处理link、cut。
link:如果不产生环显然不用考虑拓展信息。如果产生环,把这个环对应的非树边的两端的结点的拓展信息搞搞就行了。
cut:删的边如果不在环上显然不用考虑拓展信息。如果在环上,那么删边后这个环会沦为普通的树上的链。只要进行一些access再将非树边两端的结点的拓展信息搞搞就行了。
我们发现还是容易处理的。唯一头疼的就是access操作。
考虑一次换边操作:
容易列举出来:
x1->prevExMsg
x1->nextExMsg
就只有这两位爷。
于是分情况讨论下就好了:(下面的prevE、nextE是指的换边操作前的值。)
如果
x1->prevE与
x4->prevE的环编号相同,全置为空。
现在
x1->prevE与
x4->prevE的环编号不同。
先考虑x1->prevExMsg。
如果x1->prevE与x1->nextE的环编号本来就不同,那么
x1->prevExMsg不用做任何改动。
如果x1->prevE与x1->nextE的环编号相同,那么在环上往x2方向走,一直走到不能走的信息就是我们需要的。我们发现就是从x1出发往x2的方向
走的这条链的headExMsg与headMsg合并起来就是。
再考虑x1->nextExMsg。结果我们发现有点讨论不动了。怎么办?
方法就是,引入边的拓展信息!
事情至此就越发优美了。
我们定义边的prevExMsg和nextExMsg。与结点的拓展信息类似,无非就是考虑它所在的链上的前一条边和后一条边,是不是环编号相同,如果相同则没有拓展信息,如果不同则努力往外走走到不能走的路径的信息。
于是我们有如下恒等式:
x->prevExMsg = x->prevE->nextExMsg (x->prevE != NULL)
x->nextExMsg = x->nextE->prevExMsg (x->nextE != NULL)
乍一看这个好像是毫无意义的概念,其实不然。由于每条链的最顶端的结点的prevE处于很尴尬的地位,因为这条边的另一端不在这条链上。我们引进边的拓展信息弥补了这个不足。这意味着即使那条边的另一端不在这条链上,我们也能照样知道拓展信息。
我们扯出来这么一大段奇怪的话就是想说明,如下图所示的一条链,我们比原先多知道了箭头标明的那个拓展信息。(红边表示实边,黑边表示虚边)通过定义我们知道,这条顶端的黑边如果在环上,那么它的prevExMsg总是存在的。
仔细想想就能发现,仙人掌图是“边”至多在一个简单环上,所以很多性质是跟边有关的。
而lct恰好相反,维护点权才是强项。
lct维护仙人掌,水土不服。
所以才会导致我们扯出结点的拓展信息和边的拓展信息。(我曾经想过用某种类似Euler-Tour Tree这样原生态支持边信息的家伙维护……但是由于我太sb没有YY成功……如果有哪位神犇知道咋弄恳请赐教……)
实际上,我们的数据应该记录在边上,有需要时临时从一个结点的prevE和nextE获得原子信息。
这样应强调的是,一条链的最上面的那条边(即上图中顶端的黑边)的prevExMsg是不会在任何时候计入统计的。
不会计入统计?我在逗你们笑吗?没有!
我们想想为什么我们要引入这玩意儿……因为之前access的时候失败了……(好了我现在终于绕回来了……)
绕会刚才卡住的地方:
x1->nextExMsg。
但是且慢,我们要重新思考这个问题,因为真正的原子信息已经从点上转移到了边上。
(沿用之前的图中的结点编号)
哪些边的拓展信息可能被修改了呢?
容易列举出来:
x1->prevE->nextExMsg
x1->nextE->prevExMsg
x4->prevE->prevExMsg
就只有这三位爷。(比原先多了一位爷)
x1->prevE->nextExMsg:
统计方法可以照旧。
x1->nextE->prevExMsg:
x1->nextE马上要变成一条位于顶端的虚边了。
如果x1->nextE与x1->prevE的环编号不同,那么不用做任何调整。
否则,从x1往上走的那条链的tailExMsg和tailMsg,把它们合并起来作为x1->nextE->prevExMsg
x4->prevE->prevExMsg:
这家伙要想改变只有一种可能:x4->prevE与x1->prevE的环编号相同,此时置为空就好了。
于是完美解决边的拓展信息的处理。边的拓展信息变动会导致结点的拓展信息的变动,就会导致各种update。
那么哪些结点的拓展信息变了呢?其实就只有一个:x1
这样就很好办了。
下面给出access的伪代码:
void access(x) { for (p = x, q = NULL; p; q = p, p = p->fa) { splay(p); qFirstE = q ? q->msg.firstE : NULL; // 这里需要判断q为NULL的情况。 if (getCirNum(p->prevE) != NULL) // getCirNum(e)表示获取e的环编号 { if (getCirNum(p->prevE) == getCirNum(qFirstE)) { if (p->prevE) p->prevE->nextExMsg.setInvalid(); // 设为空。 if (qFirstE) qFirstE->prevExMsg.setInvalid(); } if (getCirNum(p->prevE) == getCirNum(p->next)) { // 这里的*运算表示信息的合并 p->prevE->nextExMsg = p->rc->msg.headExMsg * p->rc->msg.headMsg * path_message(p->nextE->w); // 这棵if-else树是为了讨论从x1往上走的那条链的有关信息 // path_message(w)表示边权w的边的原子信息 if (!p->lc) p->nextE->prevExMsg = path_message(p->prevE->w) * p->msg.firstE->prevExMsg; else if (p->lc->msg.tailExMsg.valid()) p->nextE->prevExMsg = path_message(p->prevE->w) * p->lc->msg.tailMsg * p->lc->msg.tailExMsg; else p->nextE->prevExMsg = path_message(p->prevE->w) * p->lc->msg.midMsg * path_message(p->msg.firstE->w) * p->msg.firstE->prevExMsg; } } p->nextE = qFirstE; p->rc = q; p->update(); } }
这样我们就顺利解决了动态仙人掌 II。
于是下面可以开心地贴代码了。有什么细节不清楚的可以看代码。(不过我觉得没啥其它细节了吧……)
温馨提示:
代码中BlockAllocator是个块状的内存分配器。是我闲得无聊加的……为了存仙人掌的边。不过地球人都知道仙人掌边数不超过2n,所以实际上开个长数组就行了。
我的access自带一次splay,所以每次没有p->update()这句,省了点常数。
path_message表示路径信息。+是并联,*是串联。
lct_message记录的是splay的一棵子树所对应的链的信息。+是串联。(虽说总复杂度是O(n log n),但是我觉得这货的大小已经快相当于一个log n了……233)
#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'); int res = c - '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 path_message { int minL; int minW; path_message(){} path_message(const int &w) : minL(w), minW(w){} path_message(const int &_minL, const int &_minW) : minL(_minL), minW(_minW){} void setEmpty() { minL = 0, minW = INF; } void setInvalid() { minL = -1, minW = -1; } bool valid() const { return minL != -1; } friend inline path_message operator+(const path_message &lhs, const path_message &rhs) { if (lhs.minL < rhs.minL) return path_message(lhs.minL, lhs.minW); else return path_message(rhs.minL, lhs.minL != rhs.minL ? rhs.minW : -1); } friend inline path_message operator*(const path_message &lhs, const path_message &rhs) { return path_message(lhs.minL + rhs.minL, min(lhs.minW, rhs.minW)); } }; struct edge { int v, u, w; }; struct lct_edge { int w; edge *cirE; path_message prevExMsg, nextExMsg; edge *getCirE() { return this ? this->cirE : NULL; } }; struct lct_message { path_message headExMsg, tailExMsg; path_message headMsg, midMsg, tailMsg; lct_edge *firstE, *lastE; bool hasCirE; void rev() { swap(firstE, lastE); swap(headExMsg, tailExMsg), swap(headMsg, tailMsg); } void coverCirE(edge *e, bool isSingle) { hasCirE = isSingle ? false : e != NULL; } friend inline lct_message operator+(const lct_message &lhs, const lct_message &rhs) { lct_message res; res.firstE = lhs.firstE, res.lastE = rhs.lastE; assert(lhs.lastE == rhs.firstE); lct_edge *e = lhs.lastE; res.hasCirE = lhs.hasCirE || e->cirE || rhs.hasCirE; if (lhs.tailExMsg.valid() && rhs.headExMsg.valid()) { res.headExMsg = lhs.headExMsg, res.headMsg = lhs.headMsg; res.tailExMsg = rhs.tailExMsg, res.tailMsg = rhs.tailMsg; res.midMsg = lhs.midMsg * (lhs.tailMsg * path_message(e->w) * rhs.headMsg + lhs.tailExMsg * path_message(e->cirE->w) * rhs.headExMsg) * rhs.midMsg; } else if (lhs.tailExMsg.valid()) { res.headExMsg = lhs.headExMsg, res.headMsg = lhs.headMsg; res.tailExMsg = lhs.tailExMsg, res.tailMsg = lhs.tailMsg * path_message(e->w) * rhs.midMsg; res.midMsg = lhs.midMsg; } else if (rhs.headExMsg.valid()) { res.headExMsg = rhs.headExMsg, res.headMsg = lhs.midMsg * path_message(e->w) * rhs.headMsg; res.tailExMsg = rhs.tailExMsg, res.tailMsg = rhs.tailMsg; res.midMsg = rhs.midMsg; } else { res.headExMsg = lhs.headExMsg, res.headMsg = lhs.headMsg; res.tailExMsg = rhs.tailExMsg, res.tailMsg = rhs.tailMsg; res.midMsg = lhs.midMsg * path_message(e->w) * rhs.midMsg; } return res; } }; struct lct_node { lct_node *fa, *lc, *rc; lct_edge *prevE, *nextE; lct_message msg; bool hasRev; bool hasCoveredCirE; edge *coveredCirE; bool isRoot() { return !fa || (fa->lc != this && fa->rc != this); } void rotate() { lct_node *x = this, *y = x->fa, *z = y->fa; lct_node *b = x == y->lc ? x->rc : x->lc; x->fa = z, y->fa = x; if (b) b->fa = y; if (z) { if (y == z->lc) z->lc = x; else if (y == z->rc) z->rc = x; } if (x == y->lc) x->rc = y, y->lc = b; else x->lc = y, y->rc = b; y->update(); } void allFaTagDown() { int anc_n = 0; static lct_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->fa->lc == fa) == (fa->lc == this)) fa->rotate(); else this->rotate(); } this->rotate(); } this->update(); } void splay_until(lct_node *target) { allFaTagDown(); while (this->fa != target) { if (fa->fa != target) { if ((fa->fa->lc == fa) == (fa->lc == this)) fa->rotate(); else this->rotate(); } this->rotate(); } lct_node *x = this; while (!x->isRoot()) x->update(), x = x->fa; x->update(); } lct_node *lmost() { lct_node *x = this; while (x->tag_down(), x->lc) x = x->lc; return x; } void access() { for (lct_node *p = this, *q = NULL; p; q = p, p = p->fa) { p->splay(); lct_edge *qFirstE = q ? q->msg.firstE : NULL; if (p->prevE->getCirE()) { if (p->prevE->getCirE() == qFirstE->getCirE()) { if (p->prevE) p->prevE->nextExMsg.setInvalid(); if (qFirstE) qFirstE->prevExMsg.setInvalid(); } if (p->prevE->getCirE() == p->nextE->getCirE()) { p->prevE->nextExMsg = p->rc->msg.headExMsg * p->rc->msg.headMsg * path_message(p->nextE->w); if (!p->lc) p->nextE->prevExMsg = path_message(p->prevE->w) * p->msg.firstE->prevExMsg; else if (p->lc->msg.tailExMsg.valid()) p->nextE->prevExMsg = path_message(p->prevE->w) * p->lc->msg.tailMsg * p->lc->msg.tailExMsg; else p->nextE->prevExMsg = path_message(p->prevE->w) * p->lc->msg.midMsg * path_message(p->msg.firstE->w) * p->msg.firstE->prevExMsg; } } p->nextE = qFirstE; p->rc = q; } this->splay(); } void makeRoot() { this->access(); this->tag_rev(); this->tag_down(); } lct_node *findRoot() { this->access(); lct_node *root = this->lmost(); root->splay(); return root; } void tag_rev() { hasRev = !hasRev; msg.rev(); } void tag_coverCirE(edge *e) { hasCoveredCirE = true, coveredCirE = e; msg.coverCirE(e, !lc && !rc); } void tag_down() { if (hasRev) { swap(lc, rc); swap(prevE, nextE); if (lc) { swap(prevE->prevExMsg, prevE->nextExMsg); lc->tag_rev(); } if (rc) { swap(nextE->prevExMsg, nextE->nextExMsg); rc->tag_rev(); } hasRev = false; } if (hasCoveredCirE) { if (lc) { prevE->cirE = coveredCirE; lc->tag_coverCirE(coveredCirE); } if (rc) { nextE->cirE = coveredCirE; rc->tag_coverCirE(coveredCirE); } hasCoveredCirE = false; } } void update() { if (prevE) msg.headExMsg = prevE->nextExMsg; else msg.headExMsg.setInvalid(); msg.headMsg.setEmpty(); if (nextE) msg.tailExMsg = nextE->prevExMsg; else msg.tailExMsg.setInvalid(); msg.tailMsg.setEmpty(); msg.midMsg.setEmpty(); msg.hasCirE = (prevE && prevE->cirE != NULL) || (nextE && nextE->cirE != NULL); msg.firstE = prevE, msg.lastE = nextE; if (lc) this->msg = lc->msg + this->msg; if (rc) this->msg = this->msg + rc->msg; } }; lct_node lctVer[MaxN + 1]; BlockAllocator<edge> lctCirEAllocator; BlockAllocator<lct_edge> lctEAllocator; int n; void cactus_init() { for (int v = 1; v <= n; v++) { lct_node *p = lctVer + v; p->fa = p->lc = p->rc = NULL; p->prevE = p->nextE = NULL; p->hasRev = false; p->hasCoveredCirE = false; p->update(); } } bool cactus_link(int v, int u, int w) { if (v == u) return false; if (v > u) swap(v, u); lct_node *x = lctVer + v, *y = lctVer + u; x->makeRoot(); y->makeRoot(); if (x->fa) { x->access(); y->splay_until(x); if (x->msg.hasCirE) return false; edge *cirE = lctCirEAllocator.allocate(); cirE->v = v, cirE->u = u, cirE->w = w; y->nextE->cirE = cirE, y->nextE->prevExMsg.setEmpty(); x->prevE->cirE = cirE, x->prevE->nextExMsg.setEmpty(); if (y->rc) y->rc->tag_coverCirE(cirE); y->update(), x->update(); } else { x->fa = y; lct_edge *e = lctEAllocator.allocate(); e->w = w, e->cirE = NULL; e->prevExMsg.setInvalid(), e->nextExMsg.setInvalid(); x->prevE = e; x->update(); } return true; } bool cactus_cut(int v, int u, int w) { if (v == u) return false; if (v > u) swap(v, u); lct_node *x = lctVer + v, *y = lctVer + u; if (x->findRoot() != y->findRoot()) return false; y->makeRoot(); x->access(); y->splay_until(x); lct_edge *e = y->nextE; edge *cirE = e->cirE; if (cirE && cirE->v == v && cirE->u == u && cirE->w == w) { y->nextE->cirE = NULL, y->nextE->prevExMsg.setInvalid(); x->prevE->cirE = NULL, x->prevE->nextExMsg.setInvalid(); if (y->rc) y->rc->tag_coverCirE(NULL); y->update(), x->update(); lctCirEAllocator.deallocate(cirE); return true; } if (!y->rc && e->w == w) { if (cirE) { y->nextE->cirE = NULL, y->nextE->prevExMsg.setInvalid(); x->prevE->cirE = NULL, x->prevE->nextExMsg.setInvalid(); } y->nextE = NULL, x->prevE = NULL; lctEAllocator.deallocate(e); x->lc = NULL, y->fa = NULL; y->update(), x->update(); if (cirE) { lct_node *ex = lctVer + cirE->v; lct_node *ey = lctVer + cirE->u; lct_node *rx = ex->findRoot(); lct_node *ry = ey->findRoot(); if (rx != ex) { ex->access(); rx->splay_until(ex); ex->prevE->cirE = NULL, ex->prevE->nextExMsg.setInvalid(); rx->nextE->cirE = NULL, rx->nextE->prevExMsg.setInvalid(); if (rx->rc) rx->rc->tag_coverCirE(NULL); rx->update(), ex->update(); } if (ry != ey) { ey->access(); ry->splay_until(ey); ey->prevE->cirE = NULL, ey->prevE->nextExMsg.setInvalid(); ry->nextE->cirE = NULL, ry->nextE->prevExMsg.setInvalid(); if (ry->rc) ry->rc->tag_coverCirE(NULL); ry->update(), ey->update(); } ex->makeRoot(), ey->makeRoot(); ex->fa = ey; e = lctEAllocator.allocate(); e->w = cirE->w, e->cirE = NULL; ex->prevE = e, ex->update(); lctCirEAllocator.deallocate(cirE); } return true; } return false; } path_message cactus_query(int qv, int qu) { lct_node *x = lctVer + qv, *y = lctVer + qu; path_message res; if (x->findRoot() != y->findRoot()) { res.setInvalid(); return res; } x->makeRoot(); y->access(); return y->msg.midMsg; } int main() { int nQ; cin >> n >> nQ; cactus_init(); while (nQ--) { char type; while (type = getchar(), type != 'l' && type != 'c' && type != 'd'); if (type == 'l') { int v = getint(), u = getint(), w = getint(); if (cactus_link(v, u, w)) printf("ok\n"); else printf("failed\n"); } else if (type == 'c') { int v = getint(), u = getint(), w = getint(); if (cactus_cut(v, u, w)) 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.minL, ret.minW); } else { puts("error!"); } } return 0; }