BZOJ.3510.首都(LCT 启发式合并 树的重心)

题目链接 BZOJ
洛谷

详见这.

求所有点到某个点距离和最短,即求树的重心。考虑如何动态维护。
两棵子树合并后的重心一定在两棵树的重心之间那条链上,所以在合并的时候用启发式合并,每合并一个点检查sz[]大的那棵子树的重心(记为root)最大子树的sz[] * 2是否>n;
若>n,则向fa移动一次(先把合并点Splay到根)。重心还一定是在sz[]大的那棵子树中,且移动次数不会超过sz[]小的子树的点数(所以总移动次数不会超过O(n)?)。
复杂度 \(O(nlog^2n)\)
具体实现。。想通了真是特别简单。。也就想想简单
先Link,然后从心向右中序遍历(深度大是向右!).
每到一个点判断其右子树和虚树的sz和 * 2是否>当前根的sz,如果是,则当前点是要找的重心,继续下一个。若sz[x] * 2 < sz[root],那么root就是根了。
可能有多个重心,这些重心应该是连续的一段。因为编号最小,若sz[x] * 2==sz[root] && id[x]<id[root],则更新root继续。
用并查集维护重心。(Find_root()太慢了)
(不需要再算左子树取max,及深度小于它的子树大小,因为既然重心在那边就不会大过另一边,更何况另一边还又插入了一棵子树)
(维护子树最大值找重心不行吧,合并更新好像很麻烦,直接用sz[]就可以找).

为什么一定要Make_root()重心。。突然觉得根本不明白LCT,不知道怎么走的了。唉以后再说吧。。先水过去
注意用点的sz[]前先Splay()更新!
至于更新重心可以通过缩短左右区间 能到O(nlogn)的方法(还比这个好理解?)以后再做吧

博客园Markdown写乘号必须要带个空格防止成斜体吗。。

//4044kb    2256ms
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
const int N=1e5+5;

#define lson son[x][0]
#define rson son[x][1]

int n,m,Ans,sz[N],sz_i[N],_fa[N],fa[N],son[N][2],sk[N];
bool tag[N];
inline void Update(int x){
    sz[x]=sz[lson]+sz[rson]+sz_i[x]+1;
}
inline bool n_root(int x){
    return son[fa[x]][0]==x||son[fa[x]][1]==x;
}
inline void Rev(int x){
    std::swap(lson,rson), tag[x]^=1;
}
inline void PushDown(int x){
    if(tag[x]) Rev(lson),Rev(rson),tag[x]=0;
}
void Rotate(int x)
{
    int a=fa[x],b=fa[a],l=son[a][1]==x,r=l^1;
    if(n_root(a)) son[b][son[b][1]==a]=x;
    if(son[x][r]) fa[son[x][r]]=a;
    fa[a]=x, fa[x]=b, son[a][l]=son[x][r], son[x][r]=a;
    Update(a);
}
void Splay(int x)
{
    int t=1,a=x; sk[1]=x;
    while(n_root(a)) sk[++t]=a=fa[a];
    while(t) PushDown(sk[t--]);
    while(n_root(x))
    {
        a=fa[x];
        if(n_root(a)) Rotate(son[a][1]==x^son[fa[a]][1]==a?x:a);
        Rotate(x);
    }
    Update(x);
}
void Access(int x){
    for(int pre=0; x; x=fa[pre=x])
        Splay(x), sz_i[x]+=sz[rson]-sz[pre], rson=pre;//Update(x);
}
void Make_root(int x){
    Access(x), Splay(x), Rev(x);
}
void Split(int x,int y){
    Make_root(x), Access(y), Splay(y);
}
int Find_root(int x)
{
    Access(x), Splay(x);
    while(lson) x=lson;
    return x;
}
void Link(int x,int y){
    Split(x,y), sz_i[y]+=sz[x], fa[x]=y, Update(y);
}
int Get_fa(int x){
    return x==_fa[x]?x:_fa[x]=Get_fa(_fa[x]);
}
//int Next(int x){//?
//  PushDown(x), x=rson;
//  while(lson) PushDown(x),x=lson;
//  return x;
//}
int q[N],cnt;
void DFS(int x,int lim)
{
    PushDown(x);
    if(lson) DFS(lson,lim);
    if(cnt>lim) return;
    q[++cnt]=x;
    if(cnt>lim) return;
    if(rson) DFS(rson,lim);
}
int Union(int x,int y)
{
    int r1=Get_fa(x),r2=Get_fa(y);
//  int r1=Find_root(x),r2=Find_root(y);
    Splay(r1), Splay(r2);//先Splay更新sz[]!
    if(sz[r1]>sz[r2]||(sz[r1]==sz[r2]&&y>x)) std::swap(x,y),std::swap(r1,r2);//x->y r1->r2
    int lim=sz[r1],res=r2,tot=sz[r2]+sz[r1];
    Link(x,y), Access(x), Splay(r2);
    cnt=0;
    DFS(r2,lim);
    for(int sum,i=1; i<=cnt; ++i)
    {
        Splay(q[i]), sum=sz[son[q[i]][1]]+sz_i[q[i]]+1;
//      if(tot<sum<<1) res=q[i];//不这么写是不是有点问题啊
//      else if(tot==sum<<1) res=std::min(res,q[i]);
        if(tot<sum<<1||(tot==sum<<1&&q[i]<=res)) res=q[i];
        else break;
    }
    Make_root(res);
    _fa[r1]=_fa[r2]=_fa[res]=res/*更新重心*/;
    return res;
}
inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}

int main()
{
    n=read(),m=read();
    int res=0,x,y; char opt[5];
    for(int i=1; i<=n; ++i) _fa[i]=i, res^=i, sz[i]=1;
    while(m--)
    {
        scanf("%s",opt);
        if(opt[0]=='A') x=read(),y=read(),res^=Get_fa(x)^Get_fa(y)^Union(x,y);
        else if(opt[0]=='Q') x=read(),printf("%d\n",Get_fa(x));//printf("%d\n",Find_root(x));
        else printf("%d\n",res);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/SovietPower/p/8634833.html

时间: 2024-09-29 01:57:20

BZOJ.3510.首都(LCT 启发式合并 树的重心)的相关文章

【BZOJ-2888】资源运输 LCT + 启发式合并

2888: 资源运输 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 63  Solved: 33[Submit][Status][Discuss] Description 小Y盯上了最近发行的即时战略游戏——ResourceTransport.但在前往通关之路的道路上,一个小游戏挡住了小Y的步伐.“国家的本质是生产与收集资源”是整款游戏的核心理念,这个小游戏也不例外.简单的说,用户需要管理一个国家,使其繁荣富强. 一个国家含有N个城市,游戏开始时

hdu 6133---Army Formations(启发式合并+树状数组)

题目链接 Problem Description > Stormtroopers were the assault/policing troops of the Galactic Empire. Dissenting citizens referred to them as bucketheads, a derogatory nickname inspired by the bucket-shaped helmets of stormtroopers. They wore white armor

[bzoj3123] [SDOI2013]森林 主席树+启发式合并+LCT

Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数.第三行包含N个非负整数表示 N个节点上的权值. 接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边, 接下来 T行,每行描述一个操作,格式为"Q x y k"或者"L x y ",其含义见题目描述部分. Output 对于每一个第一类

【BZOJ 3123】 [Sdoi2013]森林 主席树启发式合并

我们直接按父子关系建主席树,然后记录倍增方便以后求LCA,同时用并查集维护根节点,而且还要记录根节点对应的size,用来对其启发式合并,然后每当我们合并的时候我们都要暴力拆小的一部分重复以上部分,总时间复杂度为O(n*log),因为每个的节点只会作为小的部分合并,因此他所在的一小部分至少变大2倍,对于每一个节点他作为被合并方最多log次,因此其复杂度为O(n*log),而这个是绝对跑不满还差很多的,我们视他为无常数就好了,当然这一切都是建立在无拆分操作的基础之上,只要有了拆分启发式合并的复杂度就

BZOJ 2754 SCOI2012 喵星球上的点名 fail树+set启发式合并

题目大意:给定n个目标串和m个模式串,问这m个模式串每个在多少个目标串中出现过,以及n个目标串每个以最多多少个模式串为子串 我错了--就算用fail树+set启发式合并也优化不到O(nlog^2n)--这题的数据范围相当无解啊 首先将所有名字和点名的字符串全都插进AC自动机 将每个点上开一个set记录这个点是哪些喵星人的名字的前缀 然后建立fail树 沿着fail树从下到上启发式合并 每合并完一个点 如果这个点是某次点名的字符串 那么这次点名点到的喵星人就是这个点的set中的所有元素 统计答案即

BZOJ 3123 SDOI 2013 森林 可持久化线段树+启发式合并

题目大意:给出一个森林,每个节点都有一个权值.有若干加边操作,问两点之间路径上的第k小权值是多少. 思路:这题和COT1比较像,但是多了连接操作.这样就只能暴力合并连个树.启发式合并会保证时间复杂度不至于太大.然后就是用可持久化线段树维护一个树的信息,按照dfs序来建树,每个节点的可持久化链的参考版本就是它父亲的版本.之后利用权值线段树可区间加减的特性,用f[x] + f[y] - f[lca] - f[father[lca]]来计算权值. CODE: #include <cstdio> #i

BZOJ 2888 资源运输(启发式合并LCT)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2888 [题目大意] 不断加边,问每个连通块的重心到其它点的距离和的和 [题解] 启发式合并LCT,通过维护等差数列的首项和公差 来实现保存子树内所有节点到这个节点的距离之和. [代码] #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const in

BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并

题目大意:给定一棵森林,每个点有权值,提供两种操作: 1.查询两点间路径上第k小的权值 2.将两个点之间连一条边 保证连接后仍是一座森林 可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作 对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的 大小我用了并查集 其实记录根就可以了 此外本题的多组数据是在逗比 记住testcase恒等于1就是了 NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.0 样

bzoj3510 首都 LCT 维护子树信息+树的重心

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=3510 题解 首先每一个连通块的首都根据定义,显然就是直径. 然后考虑直径的几个性质: 定义:删去这个点以后剩下的连通块最大的最小的点为重心. 一棵树最多只能有两个相邻的直径: 一棵树的重心到一棵树中所有点的距离和最小.(这个也是题目的条件转化为重心的原因) 两棵树的并的重心在两棵树各自的重心的连线上. 一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置. 有了这些性质,我们可以发现,