動態仙人掌 系列題解之四——link-cut cactus
阿新 • • 發佈:2019-02-20
(重發下這篇原發於 2014-03-25 的部落格,原系列的其他三篇部落格被網易莫名禁掉了。。。所以把那三篇連同最後這篇一起搬過來)link-cut cactus首先我們回憶一下之前的做法——維護仙人掌的一棵生成樹,非樹邊作為原子資訊出現。然後我們維護生成樹的方式是用lct。也就是說我們維護一棵樹的鏈剖分。啊哈!那麼我們為什麼不能直接維護仙人掌的鏈剖分?這樣我們就得到了link-cut cactus。(……這樣命名應該沒問題?)類比link-cut tree,我們研究一下核心操作access。當然我們還是像lct一樣要找個點當根。我們規定access(x)就會把x到根的最短路全變為實邊。如果實現了這樣一個基礎操作,那麼很多事情就非常簡潔了。最短路查詢什麼的,換根什麼的,打標什麼的,想怎麼做就怎麼做。 那麼首先面臨的一個難題是,什麼是實邊,什麼是虛邊?對於不在環上的部分,顯然延續原來的lct的定義是沒問題的。但是如果有環就沒“父親”這一概念,顯得很棘手。沒辦法,那麼我們就以環為單位進行定義吧。沒有環的話就是普通情況,與lct一樣,結點有個preferred child,與之相連的邊是實邊。有環的話就是文藝情況。首先由於我們是有根的仙人掌,那麼環上肯定有一個離根最近的點,我們稱為這個環的根。即,從仙人掌的根往下走碰到的第一個在這個環上的點。對於一個環它有一個preferred child,從環的根到這個結點的最短路徑(如果有多條選任意一條)上的邊都是實邊。那麼還剩下另一半的環,我們稱為這個環的額外鏈。額外鏈的兩端是虛邊,其它邊均是實邊。 下圖是一個例子。
當然了,還有個2B情況。我們回想lct裡access(x)的過程,一開始就會把x的所有兒子邊變為虛邊。同理,當我們access一個環的根的時候,就會導致如圖所示的2B情況。注意下面三種情況是不被允許的:
那麼我們對於每個環記錄一些資訊來更方便地操作它。記錄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為空的情況過來砸場子……顯得很麻煩的樣子 = =
當然了,還有個2B情況。我們回想lct裡access(x)的過程,一開始就會把x的所有兒子邊變為虛邊。同理,當我們access一個環的根的時候,就會導致如圖所示的2B情況。注意下面三種情況是不被允許的:
那麼我們對於每個環記錄一些資訊來更方便地操作它。記錄pA為環的根,pB為環的preferred child,pEx是環的額外鏈的splay樹的根結點。
而那兩條環上的黑邊會作為額外鏈的firstE和lastE被儲存下來。不過這就讓我們不得不注意邊界情況:
對於這種情況是沒有額外鏈的,自然地pEx也就為空,這樣那條黑邊就沒人儲存了。我沒有想到什麼簡潔優美和諧統一的方式解決這個問題,於是只好多開個missingE來記錄這條邊。
但是我們考慮一棵仙人掌的換根:
於是就悲劇了。難道說不能換根了?不,可以換。我們注意到不僅是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;
}