BZOJ1095 & 动态点分治(好像应该叫点分树?)学习笔记

首先要说的是,QTREE4是从这题加强来的,这题可以用括号序列(现在还不会以后学)。

啊既然是学习笔记我来口胡一发。

觉得有这么一句话说的很好(好像是fjzzq说的),树上的动态点分治就相当于序列上的线段树,仔细一想还真有点这意思。

那首先得有个像线段树一样的结构对吧,这个结构就是用每次分治的重心串起来的,得到一颗分治树,分治树的深度大概在log级别.

然后像线段树一样,某个节点变化,只会导致它分治树上的log个父亲的信息变化,然后对于每个父亲节点我们用一些数据结构来维护,使得修改的复杂度大概在log(反正比暴力快嘛),然后修改一个点的复杂度就在log^2了。

学习fjzzq,以下的“这坨树”表示以某个节点为重心时所控制到的部分树(当然因为重心不止一个,这个结构也不固定,意思懂就ok啦)

对于这个题。我们是要从重心(下称为G),儿子中的两颗不同的子树里分别选一条路径出来。所以对于每个点维护这坨树中到它分治树上父亲节点的路径长度并扔进一个set。关于找树上两点间路径长度,我一开始想的是在分治树上爬到他们的lca然后怎么算一下,发现我是个煞笔,树上两点间路径长度是个经典问题啊。(后来fstqwq告诉我他干了很久用分治树求路径长度的事情。顺便也告诉我现在他用的是RMQ来做这个问题。)放进堆里。每个点两个堆。这个叫A堆。

幸好之前学习了很多LCA与RMQ的基情(雾)

然后我们维护下一个,对于每个点,找它分治树上的两个儿子,把他们的A堆的堆顶(也就是儿子那坨树里最长的路径)拿出来凑一下就成了(一对x)一条最长的路径。为了方便后面的操作,这个也用个堆。B堆。

然后预处理部分就搞完了。

修改一个点u的时候,一层层往上爬修改它父亲节点。
以下以开一个房间的灯为例。
首先从父亲节点fa的B堆把当前节点的堆顶删除(相当于取消贡献),然后在当前节点的A堆里把(u->fa)这条路径删除,然后再去用A堆的top更新fa的B堆。本质上是一个重新计算贡献重新up的过程。

堆的删除可以用双堆打标记的方法。暴力一点可以直接上multiset

关灯也是类似哒。然后动态点分治就学完了。qwq不过蒟蒻不敢确定这是动态点分治还是点分树。(别人也没有给明确的定义)。

不过总算学完了嘛(笑)。这下可以做SCOI2017d1t2了(然而一个星期过去了我还没做)。

听起来就知道代码比较长对吧。

//QWsin
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int logn=18;
const int maxn=100000+10;
const int maxm=200000+10;

int first[maxn],next[maxm],ecnt;
struct Edge{int u,v;Edge(int u=0,int v=0):u(u),v(v){}}e[maxm];
inline void add_edge(int u,int v){
    next[ecnt]=first[u];first[u]=ecnt;e[ecnt++]=Edge(u,v);
    next[ecnt]=first[v];first[v]=ecnt;e[ecnt++]=Edge(v,u);
}

int anc[maxn][logn+2],dep[maxn];
inline void dfs_anc(int u,int pre)
{
    anc[u][0]=pre;dep[u]=dep[pre]+1;
    for(int i=1;(1<<i)+1<=dep[u];++i) anc[u][i]=anc[anc[u][i-1]][i-1];
    for(int i=first[u];i!=-1;i=next[i])
        if(e[i].v!=pre) dfs_anc(e[i].v,u);
}

int n;

inline void init_data()
{
    cin>>n;memset(first,-1,sizeof first);
    for(int i=1,u,v;i<n;++i){
        scanf("%d%d",&u,&v);
        add_edge(u,v);
    }
}

int top,path[maxn],sz[maxn],Msz[maxn],done[maxn];

void DP(int u,int pre)
{
    path[++top]=u;
    sz[u]=1;Msz[u]=0;
    for(int i=first[u];i!=-1;i=next[i])
    {
        int v=e[i].v;   
        if(done[v]||v==pre) continue;
        DP(v,u);
        sz[u]+=sz[v];Msz[u]=max(Msz[u],sz[v]);
    }
}

inline int find_G(int u)
{
    top=0;
    DP(u,u);
    int m=Msz[u],pos=u;
    for(int i=2;i<=top;++i)
    {
        int t=max(Msz[path[i]],sz[u]-sz[path[i]]);
        if(t <= m) {m=t;pos=path[i];}   
    }
    return pos;
}

inline int dis(int a,int b)
{
    int x=a,y=b;
    if(dep[a] > dep[b]) swap(a,b);
    if(dep[a] < dep[b]){
        int t=dep[b]-dep[a];
        for(int i=logn;i>=0;--i)
            if(t-(1<<i) >= 0){
                t-=1<<i;b=anc[b][i];
            }
    }
    if(a!=b) 
    {
        for(int i=logn;i>=0;--i)
            if(anc[a][i]!=anc[b][i])
                a=anc[a][i],b=anc[b][i];
        a=anc[a][0];b=anc[b][0];
    }
    return dep[x]+dep[y]-2*dep[a];
}

struct PQ{
    priority_queue<int>q1,q2;
    inline void push(const int &x){q1.push(x);}
    inline void erase(const int &x){q2.push(x);}
    inline void pop(){
        while(q2.size() && q1.top()==q2.top()) q1.pop(),q2.pop();
        q1.pop();
    }
    inline int top(){
        while(q2.size()&&(q1.top()==q2.top())) q1.pop(),q2.pop();
        return q1.top();
    }
    inline int top2(){
        int t=top();pop();
        int ret=top();push(t);
        return ret;
    }
    inline int size(){return q1.size()-q2.size();}
}f[maxn],g[maxn],ans;//f[u]记到G的距离,g[u]记分治树上儿子的f[u]堆顶 

inline void work(int u,int pre,const int &G,const int &fa)
{
    f[G].push(dis(u,fa));
    for(int i=first[u];i!=-1;i=next[i])
    {
        int v=e[i].v; 
        if(done[v]||v==pre) continue;   
        work(v,u,G,fa);
    }
}

inline void insert(PQ &q){
    if(q.size() >= 2)   ans.push(q.top()+q.top2());
}

inline void erase(PQ &q){
    if(q.size() >= 2)   ans.erase(q.top()+q.top2());
}

int p[maxn];//分治树上父亲 
inline int DivTree(int u,int fa)
{
    if(u==2){
        int stop=1; 
    }

    int G=find_G(u);
    g[G].push(0);//一切以G为中心!! 
    work(G,G,G,fa);
    done[G]=1;
    for(int i=first[G];i!=-1;i=next[i])
    {
        int v=e[i].v;
        if(done[v]) continue;
        int Gy=DivTree(v,G);
        p[Gy]=G;

//      cout<<f[v].size();

        g[G].push(f[Gy].top());//注意构建的时候要用Gy不能用v,因为我Gy才是分治树上G的儿子 
    }
    insert(g[G]);
    return G;
}

inline void del(int u)
{
    erase(g[u]);
    g[u].erase(0);
    insert(g[u]);

    for(int t=u;p[t];t=p[t])
    {
        erase(g[p[t]]);
//      cout<<"*"<<g[p[t]].size()<<endl;
        g[p[t]].erase(f[t].top());
        f[t].erase(dis(u,p[t]));
        if(f[t].size()) g[p[t]].push(f[t].top());
        insert(g[p[t]]);
    }
}
inline void add(int u)
{
    erase(g[u]);
    g[u].push(0);
    insert(g[u]);

    for(int t=u;p[t];t=p[t])
    {
        erase(g[p[t]]);
        if(f[t].size()) g[p[t]].erase(f[t].top());
        f[t].push(dis(u,p[t]));
        g[p[t]].push(f[t].top());
        insert(g[p[t]]);
    }
}

int col[maxn];
inline void solve()
{
    int m,cntw=n;cin>>m;
    char op[5];int a;

    while(m--)
    {
        scanf("%s",op);
        if(op[0]=='G') 
        {
            if(cntw<=1) printf("%d\n",cntw-1); 
            else printf("%d\n",ans.top());
        }
        else{
            //反色并update 
            scanf("%d",&a);
            if(!col[a]) {--cntw;del(a);}
            else{++cntw;add(a);}
            col[a]^=1;
        }
    }
}

int main()
{
    init_data();
    dfs_anc(1,1);
    DivTree(1,0);
    solve();

    return 0;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值