1. 程式人生 > >動態仙人掌 系列題解之四——link-cut cactus

動態仙人掌 系列題解之四——link-cut cactus

(重發下這篇原發於 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為空的情況過來砸場子……顯得很麻煩的樣子 = =
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;
}