bzoj3510首都 LCT维护子树信息+启发式合并

 

3510: 首都

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 213  Solved: 82
[Submit][Status][Discuss]

Description

在X星球上有N个国家,每个国家占据着X星球的一座城市。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。 
X星球上战乱频发,如果A国打败了B国,那么B国将永远从这个星球消失,而B国的国土也将归A国管辖。A国国王为了加强统治,会在A国和B国之间修建一条公路,即选择原A国的某个城市和B国某个城市,修建一条连接这两座城市的公路。 
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。 
现在告诉你发生在X星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理: 
1、A x y:表示某两个国家发生战乱,战胜国选择了x城市和y城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。 
2、Q x:询问当前编号为x的城市所在国家的首都。 
3、Xor:询问当前所有国家首都编号的异或和。 

Input

第一行是整数N,M,表示城市数和需要处理的信息数。 
接下来每行是一个信息,格式如题目描述(A、Q、Xor中的某一种)。 

Output

输出包含若干行,为处理Q和Xor信息的结果。 

Sample Input

10 10
Xor
Q 1
A 10 1
A 1 4
Q 4
Q 10
A 7 6
Xor
Q 7
Xor

Sample Output

11
1
1
1
2
6
2

HINT

 

对于100%的数据,2<=N<=100000,1<=M<=200000。 
 

 

Source

 

 

 

呼呼,好难!!(蒟蒻伤不起)
启发式合并+LCT维护子树信息
首先讲讲启发式合并
听起来很高大上,实际很暴力
就是,每次新加一个节点,就把整棵树重新添加一遍(在LCT中)
据说只要把小的合到大的里面,就可以实现log
特别神奇有木有。
啊啦啊啦,别看现在几句话,已经要多开好几个数组了(敲黑板!)
1.FA[]记录并查集2.SIZ[]记录并查集大小3.struct edge用来建出真正的树
然后合并的时候,就把小的树一个一个添加到大的树中就可以了
代码看看:

 

if(opt == 'A') {
            int x = read(), y = read(), fx = find(x), fy = find(y);
            if(sz[fx] > sz[fy]) swap(x, y), swap(fx, fy);
            sz[fy] += sz[fx];
            ans ^= rt[fx]; F[fx] = fy;
            dfs(x, y);
            adds(x, y);
        }
void dfs(int x, int f) {
    ch[x][0] = ch[x][1] = fa[x] = rev[x] = 0;
    isiz[x] = 0;
    siz[x] = 1;
    Link(x, f);
    for(int i = pre[x]; i; i = e[i].next) 
    if(e[i].to != f) 
        dfs(e[i].to, x);
}

然后,就是LCT维护子树信息了
首先看看这个blog,说的贼好,%%%:戳这里
众所周知(如果这句话伤害到了您,请原谅),如果一个节点的某个子树大小的两倍大于整棵树的大小,那么这颗子树的根节点一定比原节点更优。
所以在暴力插树Link时“顺便”移动重心就可以了。就可以了
代码:

void Link(int p, int g) {
    makeroot(p); makeroot(g);
    fa[p] = g;
    isiz[g] += siz[p];
    update(g);
    int fg = find(g);
    makeroot(rt[fg]);
    Access(p);
    Splay(rt[fg]);
    int S = siz[rt[fg]];
    int t = ch[rt[fg]][1];
    push_down(t);
    while(ch[t][0]) {
        t = ch[t][0];
        push_down(t);
    }
    Access(t);
    if(((isiz[t] + 1) << 1) > S || ((isiz[t] + 1) << 1) == S && t < rt[fg]) {
        ans ^= rt[fg] ^ t;
        rt[fg] = t;
    } 
}

其他都是LCT套路和细节与细节与细节不解释了
全代码:

 

 

#include
#include
#include
#include
#include
#include
#include
#define maxn 220000
#define ls ch[p][0]
#define rs ch[p][1]
using namespace std;
int read() {
    char ch = getchar(); int x = 0, f = 1;
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 - '0' + ch; ch = getchar();}
    return x * f;
}
 
int tot, pre[maxn];
struct edge {
    int to, next;
    void add(int u, int v) {
        to = v; next = pre[u];
        pre[u] = tot;
    }
}e[maxn << 2];
void adds(int u, int v) {
    e[++tot].add(u, v);
    e[++tot].add(v, u);
}
 
int ch[maxn][2], fa[maxn], siz[maxn], isiz[maxn], sz[maxn];
bool rev[maxn];
int F[maxn], rt[maxn], st[maxn], n, m, ans, top;
bool wh(int p) {return ch[fa[p]][1] == p;}
bool Isroot(int p) {return ch[fa[p]][0] != p && ch[fa[p]][1] != p;} 
void update(int p) {siz[p] = siz[ls] + siz[rs] + isiz[p] + 1;}
void push_down(int p) {
    if(rev[p]) {
        if(ls) rev[ls] ^= 1; if(rs) rev[rs] ^= 1;
        rev[p] ^= 1; swap(ls, rs);
    }
}
void push_up(int p) {
    st[top = 1] = p;
    for(int i = p; !Isroot(i); i = fa[i]) st[++top] = fa[i];
    for(int i = top; i; --i) push_down(st[i]);
}
 
int find(int x) {return F[x] == x ? x : F[x] = find(F[x]);}
  
void Rotate(int p) {
    int f = fa[p], g = fa[f], c = wh(p);
    if(!Isroot(f)) ch[g][wh(f)] = p; fa[p] = g;
    ch[f][c] = ch[p][c ^ 1]; if(ch[f][c]) fa[ch[f][c]] = f;
    ch[p][c ^ 1] = f; fa[f] = p;
    update(f);
}
  
void Splay(int p) {
    push_up(p);
    for(; !Isroot(p); Rotate(p)) 
        if(!Isroot(fa[p])) Rotate(wh(fa[p]) == wh(p) ? fa[p] : p);
    update(p);
}
  
void Access(int p) {
    for(int pre = 0; p; pre = p, p = fa[p]) {
        Splay(p);
        isiz[p] += siz[rs] - siz[pre];
        rs = pre;
        update(p);
    }
}
 
void makeroot(int p) {Access(p); Splay(p); rev[p] ^= 1;}
void Link(int p, int g) {
    makeroot(p); makeroot(g);
    fa[p] = g;
    isiz[g] += siz[p];
    update(g);
    int fg = find(g);
    makeroot(rt[fg]);
    Access(p);
    Splay(rt[fg]);
    int S = siz[rt[fg]];
    int t = ch[rt[fg]][1];
    push_down(t);
    while(ch[t][0]) {
        t = ch[t][0];
        push_down(t);
    }
    Access(t);
    if(((isiz[t] + 1) << 1) > S || ((isiz[t] + 1) << 1) == S && t < rt[fg]) {
        ans ^= rt[fg] ^ t;
        rt[fg] = t;
    } 
}
 
void dfs(int x, int f) {
    ch[x][0] = ch[x][1] = fa[x] = rev[x] = 0;
    isiz[x] = 0;
    siz[x] = 1;
    Link(x, f);
    for(int i = pre[x]; i; i = e[i].next) 
    if(e[i].to != f) 
        dfs(e[i].to, x);
}
  
int main()
{
    n = read();
    for(int i = 1;i <= n; ++i) {
        F[i] = rt[i] = i;
        sz[i] = siz[i] = 1;
        ans ^= i;
    }
    m = read();
    while(m--) {
        char opt = getchar();
        while(opt < 'A' || opt > 'Z') opt = getchar();
        if(opt == 'X') printf("%d\n", ans);
        if(opt == 'A') {
            int x = read(), y = read(), fx = find(x), fy = find(y);
            if(sz[fx] > sz[fy]) swap(x, y), swap(fx, fy);
            sz[fy] += sz[fx];
            ans ^= rt[fx]; F[fx] = fy;
            dfs(x, y);
            adds(x, y);
        }
        if(opt == 'Q') printf("%d\n", rt[find(read())]);
    }
    return 0;
}
/*
10 1000000
A 1 2
A 2 3
A 1 4
Q 1
Q 2
Q 3
Q 4
A 2 5
Q 1
Q 2
Q 3
Q 4 
Xor
A 2 3
A 2 4
A 2 5
Q 2
Xor
A 6 7
A 6 8
A 6 9
A 6 10
Q 6
Xor
Q 5
Q 2
Q 6
Xor
*/

UPD:2019.4.9

有一种更简便的O(nlog)的做法

一个结论是,两颗树合并后的重心肯定在原来两颗树重心的路径上。

把这条路径Access一下,然后在链上二分。

二分的时候只需要考虑这个点在实边方向上的两个子树的大小是否都小于当前整棵树的一半。

否则的话往大的那一块跳即可。

证明的话,首先有个结论是,如果某个节点的size大小的两倍比当前树大小大,那么往这个子树移动肯定优秀。

那么不能挪动就意味着两个方向都比当前树的一半小。又因为重心在连线上,所以不可能往虚子树移动。

用的是luogu4299提交的

// luogu-judger-enable-o2
#include
#define ls ch[p][0]
#define rs ch[p][1]
const int N = 1e5 + 10;
int ri() {
	char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int Ans, n, m, G[N], g[N], ch[N][2], sz[N], isz[N], fa[N], st[N], tp;
bool rev[N];
int F(int x) {return !g[x] ? x : g[x] = F(g[x]);}
bool wh(int p) {return ch[fa[p]][1] == p;}
bool Ir(int p) {return ch[fa[p]][0] != p && ch[fa[p]][1] != p;}
void Up(int p) {sz[p] = sz[ls] + sz[rs] + isz[p] + 1;}
void Rotate(int p) {
	int f = fa[p], g = fa[f], c = wh(p);
	if(!Ir(f)) ch[g][wh(f)] = p; fa[p] = g;
	ch[f][c] = ch[p][c ^ 1]; if(ch[f][c]) fa[ch[f][c]] = f;
	ch[p][c ^ 1] = f; fa[f] = p; Up(f);
}
void Rev(int p) {rev[p] ^= 1; std::swap(ls, rs);}
void Push(int p) {
	if(rev[p]) {
		if(ls) Rev(ls);
		if(rs) Rev(rs);
		rev[p] = 0;
	}
}
void Splay(int p) {
	st[tp = 1] = p;
	for(int i = p; !Ir(i); i = fa[i])
		st[++tp] = fa[i];
	for(int i = tp; i; --i)
		Push(st[i]);
	for(;!Ir(p); Rotate(p))
		if(!Ir(fa[p])) Rotate(wh(fa[p]) == wh(p) ? fa[p] : p);
	Up(p);
}
void Access(int u) {
	for(int p = u, pr = 0; p; pr = p, p = fa[p]) {
		Splay(p);
		if(pr) isz[p] -= sz[pr];
		if(rs) isz[p] += sz[rs];
		rs = pr; 
		Up(p);		
	}
	Splay(u);
}
void Makeroot(int p) {Access(p); Rev(p);}
void Link(int u, int v) {
	Makeroot(u); Access(v);
	fa[u] = v; isz[v] += sz[u]; sz[v] += sz[u];
	u = F(u); v = F(v);
	int gu = G[u], gv = G[v];
	Makeroot(gu); Access(gv);
	int p = gv, x = sz[p] >> 1, lsiz = 0, rsiz = 0, nG = n;
	bool od = sz[p] & 1;
	for(;p;) {
		Push(p);
		int nl = sz[ls] + lsiz, nr = rsiz + sz[rs];
		if(nl <= x && nr <= x) {
			if(od) {nG = p; break;}
			else if(nG > p) nG = p;
		}
		if(nl < nr)
			lsiz += sz[ls] + isz[p] + 1, p = rs;
		else
			rsiz += sz[rs] + isz[p] + 1, p = ls;
	}
	Ans ^= gu ^ gv ^ nG;
	g[u] = v; G[v] = nG;
}
int main() {
	n = ri(), m = ri();
	for(int i = 1;i <= n; ++i)
		sz[i] = 1, G[i] = i, Ans ^= i;
	for(;m--;) {
		char op = getchar(); for(;op < 'A' || op > 'Z'; op = getchar()) ;
		if(op == 'A')
			Link(ri(), ri());
		if(op == 'Q') 
			printf("%d\n", G[F(ri())]);
		if(op == 'X')
			printf("%d\n", Ans);
	}
	return 0;
}

 

 

 

你可能感兴趣的:(bzoj3510首都 LCT维护子树信息+启发式合并)