[BZOJ2888]資源運輸 (LCT+啓發式合併)

很多個連通塊,每次合併兩個,保證是森林。一個連通塊的代價爲所有節點到該塊的重心距離之和。動態不停連邊,詢問森林中各連通塊代價之和。

這題確實有很多地方都很巧妙,看着claris的題解和程序才寫了出來,漲了不少姿勢。。

首先如果已知每個樹的重心,用LCT的link直接將兩棵樹合併的話不是很好求出新樹的重心。但是如果一次只添加一個葉子,可以保證重心要麼不變,要麼向新加入的葉子的方向移動一下。因此我們啓發式合併,把小的樹拆掉以添葉子的形式加入大的樹中。

然後爲了維護重心,我們要在LCT上動態維護子樹大小。這個直接用統計來維護不是很好搞,但是如果轉換爲計算貢獻,添加一個葉子所產生的對應的貢獻就是它到根的路徑上所有節點的子樹大小+1,轉化爲鏈修改。同時爲了計算代價,還要維護子樹中所有節點到自身的距離之和,仍然考慮計算貢獻的方法,添加一個葉子節點對一條鏈上的節點增加一個等差數列即可。又漲姿勢啦!等差數列的標記可以保存一個首項和一個公差,通過size域來計算它應該增加多少。還有這種計算貢獻的方法侷限於根不變的樹,所以不能用splay的區間翻轉來換根。

然後考慮重心的挪動的時候要用類似K-鏟雪車的那種樹DP的思路,維護子樹,子樹距離之和這兩個量。由於重心至多往葉子節點方向移動一下,只需要手動swap一下即可完成換根,就可以不放rev標記了。

啓發:利用啓發式合併將合併子樹轉化爲添加葉子;考慮計算貢獻的方法來維護子樹信息;不換跟的LCT操作。

#include<algorithm>
#include<cstdio>
#include<cstring>
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define lch(x) ch[x][0]
#define rch(x) ch[x][1]
#define link alewatte
#define access laugaitwe
using namespace std;
const int MAXN = 40005;

inline void get(int&r)
{
	register char c, f=0; r=0;
	do {c=getchar();if(c=='-')f=1;} while(c<'0'||c>'9');
	do r=r*10+c-'0',c=getchar(); while(c>='0'&&c<='9');
	if (f) r = -r;
}

int N, M, ans;

struct Ed {
	int to; Ed*nxt;
} Edges[MAXN*2], *ecnt=Edges, *adj[MAXN];
void adde(int a, int b)
{
	(++ecnt)->to = b;
	ecnt->nxt = adj[a];
	adj[a] = ecnt;
}

struct LCT
{
	int ch[MAXN][2], fa[MAXN], pfa[MAXN];
	int sz[MAXN], sub[MAXN], sum[MAXN];
	int ta[MAXN], ts[MAXN], td[MAXN];
	int sta[MAXN], tp;
	void init()
	{
		rep(i, 1, N) sz[i] = sub[i] = 1;
	}
	inline void add1(int x, int tag)
	{
		if (!x) return;
		sub[x] += tag;
		ta[x] += tag;
	}
	inline void add2(int x, int s, int d)
	{
		if (!x) return;
		sum[x] += s+sz[rch(x)]*d;
		ts[x] += s, td[x] += d;
	}
	inline void pushdown(int x)
	{
		if (ta[x])
		{
			add1(lch(x), ta[x]);
			add1(rch(x), ta[x]);
			ta[x] = 0;
		}
		if (td[x])
		{
			add2(lch(x), ts[x]+(sz[rch(x)]+1)*td[x], td[x]);
			add2(rch(x), ts[x], td[x]);
			ts[x] = td[x] = 0;
		}
	}
	inline void pushup(int x)
	{
		sz[x] = sz[lch(x)] + sz[rch(x)] + 1;
	}
	inline void rotate(int x)
	{
		int y = fa[x];
		int d = (x==lch(y));
		ch[y][!d] = ch[x][d];
		if (ch[x][d]) fa[ch[x][d]] = y;
		fa[x] = fa[y];
		if (fa[y]) ch[fa[y]][y==rch(fa[y])] = x;
		pfa[x] = pfa[y], pfa[y] = 0;
		fa[y] = x, ch[x][d] = y;
		pushup(y);
	}
	void splay(int x)
	{
		sta[tp=1] = x;
		for (int i=x; fa[i]; i=fa[i]) sta[++tp] = fa[i];
		while (tp) pushdown(sta[tp--]);
		for (int y, z; fa[x]; rotate(x))
		{
			y = fa[x], z = fa[y];
			if (z) rotate(((y==lch(z))^(x==lch(y))) ? x : y);
		}
		pushup(x);
	}
	void access(int x)
	{
		for (int y = 0; x; x=pfa[x])
		{
			if (splay(x), rch(x)) fa[rch(x)] = 0, pfa[rch(x)] = x;
			rch(x) = y, pfa[y] = 0, fa[y] = x;
			pushup(y = x);
		}
	}
	int root(int x)
	{
		access(x), splay(x);
		while (lch(x)) pushdown(x), x = lch(x);
		return splay(x), x;
	}
	void addleaf(int x, int y)
	{
		pfa[y] = x, fa[y] = 0, sz[y] = 1;
		lch(y) = rch(y) = sub[y] = 0;
		sum[y] = ta[y] = ts[y] = td[y] = 0;
		x = root(x), access(y), splay(x);
		add1(x, 1), add2(x, 0, 1);
		for (y=rch(x); lch(y); y=lch(y));
		splay(y);
		int vx = sub[x], vy = sub[y];
		if (vy*2 > vx)
		{
			sub[y] = vx, sub[x] -= vy;
			sum[x] -= sum[y]+vy;
			sum[y] += sum[x]+vx-vy;
			access(y), splay(x);
			swap(lch(x), rch(x));
		}
	}
} tr;

void DFS(int u, int fa)
{
	tr.addleaf(fa, u);
	for (Ed*p = adj[u]; p; p=p->nxt)
		if (p->to != fa) DFS(p->to, u);
}

void lian(int u, int v)
{
	int x = tr.root(u), y = tr.root(v);
	ans -= tr.sum[x] + tr.sum[y];
	if (tr.sub[x] < tr.sub[y]) swap(u, v); //把v往u上栽
	DFS(v, u), adde(u, v), adde(v, u);
	ans += tr.sum[tr.root(u)];
}

int main()
{
	get(N), get(M);
	tr.init();
	char op;
	int u, v;
	while (M --)
	{
		do op=getchar(); while(op!='A'&&op!='Q');
		if (op=='A') get(u), get(v), lian(u, v);
		else printf("%d\n", ans);
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章