(重发下这篇原发于 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;
}