很多个连通块,每次合并两个,保证是森林。一个连通块的代价为所有节点到该块的重心距离之和。动态不停连边,询问森林中各连通块代价之和。
这题确实有很多地方都很巧妙,看着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;
}