BZOJ 3435 Wc2014 紫荆花之恋 动态树分治+替罪羊树+Treap

11 篇文章 0 订阅
9 篇文章 0 订阅

题目大意:给定一棵树,每次添加一个节点并询问当前有多少点对满足dis(i,j)<=ri+rj 强制在线


吾辈有生之年终于把这道题切了。。。QAQ


什么?你想做这题?

1095切了么?没?去把1095切掉再说!

3065切了么?没?去把3065切掉再说!

什么?都切了?那还不会做这题??


……


算了还是说说做法吧。。。


我们抛开那些乱七八糟的,考虑朴素做法


首先式子的形式是dis(i,j)<=ri+rj,令p=lca(i,j),把式子变形可以得到dis(j,p)-rj<=ri-dis(i,p)

那么对于每个p我们维护一个Treap,把以p为根的子树中所有的dis(j,p)-rj全都扔进去

然后对于每个i,我们沿着父亲指针扫一遍,把路径上所有Treap中小于等于ri-dis(i,p)的点全都计入答案,然后把dis(i,p)-ri扔进去

但是这样会有重复的,即lca(i,j)!=p的情况,那么我们对于p的每个子树再维护一个Treap储存dis(j,p)-rj,从对应子树的Treap中减掉这样的点对就行了


这不会TLE+MLE?

废话这当然会TLE+MLE。。。


考虑树的形态是一条链的时候,时间复杂度会退化为O(n^2logn),空间复杂度也会退化到O(n^2)。

那么我们可以考虑替罪羊树的思想,即如果某个子树的大小超过了父亲节点为根的子树大小的0.88,就把以父亲节点为根的子树暴力重建

既然要重建肯定要建成相对平衡一些的树辣。。。 于是点分治不就好了辣。。。。


说起来真是简单呢~~

于是这题我写了整整七个小时。。。写到后半夜两点才过掉。。。

主要是重建之后新的子树与原先的父节点连接很不好。。。


我が生涯に、一片の悔いなし。。。


UPD:感谢orzGEOTCBRL提供的代码注释- -


#include <set>  
#include <queue>  
#include <cstdio>  
#include <cstring>  
#include <iostream>  
#include <algorithm>  
#define M 100100  
#define ALPHA 0.88  
using namespace std;  
  
int n;  
long long last_ans;  
int r[M];  
  
namespace Graph{  //我们把这棵树除了构建点分树之外再构建一棵根为1的树,用来查询两点之间的距离 
   
    struct abcd{  
        int to,f,next; //终点,边权,下一条边 
        int ban;  //这条边能不能走(会不会走出这棵子树) 
    }table[M<<1];  //边表 
    int head[M]/*每个点的邻接表*/,tot=1/*边数*/;  
  
    int ancestor[M][17]/*表示x向上2^j的祖先 */,dpt[M]/*深度*/,dis[M]/*走到根的路径权值和*/;  
  
    void Add(int x,int y,int z)  //加边 
    {  
        table[++tot].to=y;  
        table[tot].f=z;  
        table[tot].next=head[x];  
        head[x]=tot;  
    }  
  
    void Build_LCA(int x)  //构建 ancestor[x][j]表示x向上2^j的祖先 
    {  
        int j;  
        for(j=1;j<=16;j++)  
            ancestor[x][j]=ancestor[ancestor[x][j-1]][j-1];  
    }  
  
    int LCA(int x,int y)  //求lca 
    {  
        int j;  
        if(dpt[x]<dpt[y])  
            swap(x,y);  
        for(j=16;~j;j--)  
            if(dpt[ancestor[x][j]]>=dpt[y])  
                x=ancestor[x][j];  
        if(x==y) return x;  
        for(j=16;~j;j--)  
            if(ancestor[x][j]!=ancestor[y][j])  
                x=ancestor[x][j],y=ancestor[y][j];  
        return ancestor[x][0];  
    }  
  
    int Distance(int x,int y)  //求两点距离 
    {  
        int lca=LCA(x,y);  
        return dis[x]+dis[y]-2*dis[lca];  
    }  
  
}  
  
struct Treap{  
  
    static queue<Treap*> bin;  //垃圾桶,用来写垃圾回收 
      
    Treap *ls,*rs;  //左右子树 
    int val,key;  //键值,优先级 
    int cnt,size;  //cnt表示这个数的个数,size表示这个点子树的大小 
  
    void* operator new (size_t,int _)  
    {  
        Treap *re;  
        if(bin.size())  
            re=bin.front(),bin.pop();  
        else  
        {  
            static Treap *mempool,*C;  
            if(C==mempool)  
                mempool=(C=new Treap[1<<16])+(1<<16);  
            re=C++;  
        }  
        re->ls=re->rs=0x0;  
        re->val=_;re->key=rand();//信仰!  
        re->cnt=re->size=1;  
        return re;  
    }  
  
    void operator delete (void *p)  
    {  
        bin.push((Treap*)p);  
    }  
  
    void Push_Up()  //maintain
    {  
        size=cnt;  
        if(ls) size+=ls->size;  
        if(rs) size+=rs->size;  
    }  
  
    friend void Zig(Treap *&x)  
    {  
        Treap *y=x->ls;  
        x->ls=y->rs;  
        y->rs=x;x=y;  
        x->rs->Push_Up();  
        x->Push_Up();  
    }  
  
    friend void Zag(Treap *&x)  
    {  
        Treap *y=x->rs;  
        x->rs=y->ls;  
        y->ls=x;x=y;  
        x->ls->Push_Up();  
        x->Push_Up();  
    }  
  
    friend void Insert(Treap *&x,int y)  
    {  
        if(!x)  
        {  
            x=new (y)Treap;  
            return ;  
        }  
        if(y==x->val)  
            x->cnt++;  
        else if(y<x->val)  
        {  
            Insert(x->ls,y);  
            if(x->ls->key>x->key)  
                Zig(x);  
        }  
        else  
        {  
            Insert(x->rs,y);  
            if(x->rs->key>x->key)  
                Zag(x);  
        }  
        x->Push_Up();  
    }  
  
    friend void Decomposition(Treap *&x)  //删除整棵树 
    {  
        if(!x) return ;  
        Decomposition(x->ls);  
        Decomposition(x->rs);  
        delete x;x=0x0;  
    }  
  
    friend int Query(Treap *x,int y)  //求比y小的数的个数 
    {  
        if(!x) return 0;  
        if(y<x->val)  
            return Query(x->ls,y);  
        else  
            return (x->ls?x->ls->size:0) + x->cnt + Query(x->rs,y);  
    }  
  
};  
  
queue<Treap*> Treap :: bin;  
  
namespace Dynamic_TDC{  
  
    using namespace Graph;  
  
    int fa[M]/*点分树上的fa*/,v[M],T;  
  
    Treap *tree1[M]/*这个点集所有点的权值构成的treap*/,*tree2[M]/*这个点集一棵子树的权值构成的treap*/;  
  
    set<int> to[M];  //点分树上的儿子 
      
    void DFS(int x)  //删除整棵子树(这里指点分树上的子树) 
    {  
        set<int>::iterator it;  
        v[x]=T;  
        for(it=to[x].begin();it!=to[x].end();it++)  //枚举点分树上的所有儿子 
        {  
            DFS(*it);  
            Decomposition(tree2[*it]);  
        }  
        to[x].clear();  
        Decomposition(tree1[x]);  
    }  
  
    int Get_Size(int x,int from)  //求树的大小 
    {  
        int i,re=1;  
        for(i=head[x];i;i=table[i].next)  //遍历从这个点出发的每条边 
        {  
            if(v[table[i].to]!=T)  
                continue;  
            if(table[i].ban==T)  
                continue;  
            if(table[i].to==from)  
                continue;  //记录上一个点,这个点不能回到上一个点 
            re+=Get_Size(table[i].to,x);  
        }  
        return re;  
    }  
  
    int Get_Centre_Of_Gravity(int x,int from,int size,int &cg)  //求重心 
    {  
        int i,re=1,flag=true;  
        for(i=head[x];i;i=table[i].next)  //遍历从这个点出发的每条边 
        {  
            if(v[table[i].to]!=T)  
                continue;  
            if(table[i].ban==T)  
                continue;  
            if(table[i].to==from)  
                continue;  //记录上一个点,这个点不能回到上一个点 
            int temp=Get_Centre_Of_Gravity(table[i].to,x,size,cg);  
            if(temp<<1>size) flag=false;  
            re+=temp;   
        }  
        if(size-re<<1>size) flag=false;  
        if(flag) cg=x;  
        return re;  
    }  
  
    void DFS(int x,int from,int dpt,Treap *&p)  //把所有在这棵子树的dep[x]-r[x]插入到treap中 
    {  
        int i;  
        Insert(p,dpt-r[x]);  
        for(i=head[x];i;i=table[i].next)  
        {  
            if(v[table[i].to]!=T)  
                continue;  
            if(table[i].ban==T)  
                continue;  
            if(table[i].to==from)  
                continue;  
            DFS(table[i].to,x,dpt+table[i].f,p);  
        }  
    }  
      
    int Tree_Divide_And_Conquer(int x)  //点分 
    {  
        int i,size=Get_Size(x,0);  //得到整棵树的size 
        Get_Centre_Of_Gravity(x,0,size,x);  //得到重心,在这之后x为根 
        DFS(x,0,0,tree1[x]);  //把所有在这棵子树的dep[x]-r[x]插入到treap中
        for(i=head[x];i;i=table[i].next)  
        {  
            if(v[table[i].to]!=T)  
                continue;  
            if(table[i].ban==T)  
                continue;  
              
            Treap *p=0x0;  
            DFS(table[i].to,x,table[i].f,p);  //把所有在这棵子树的dep[x]-r[x]插入到treap中
              
            table[i].ban=table[i^1].ban=T;  //这条边会通向上一个分治中心,如果走了这条边就会走到外面 
              
            int temp=Tree_Divide_And_Conquer(table[i].to);  //分治,递归各子树,temp为各子树的重心 
            tree2[temp]=p;fa[temp]=x;/*不是树上的fa,是点分树上的fa*/to[x].insert(temp);  
        }  
        return x;  
    }  
  
    void Rebuild(int x)  
    {  
        ++T;/*新建点分树上的节点*/DFS(x);  //把x的子树直接整棵删除 
        int y=fa[x];  //得到点分树上的父亲 
        Treap *p=tree2[x];  //tree2是以y为根时x这个点集的dep[i]-r[i]所构成的treap,所以不会改变,应当保留 
        tree2[x]=0x0;  
        int temp=Tree_Divide_And_Conquer(x);   //重新点分之后的节点 (重构后的这棵点分树的子树的根)
        fa[temp]=y;if(y) to[y].insert(temp);  //此处因为不仅要插入新的子节点,还要把原来的删掉,所以要加一句to[y].erase(x); 
        tree2[temp]=p;  //保留原来的tree2 
    }  
  
    void Insert(int x)  //在点分树上插入新节点x 
    {  
        int i;  
          
        for(i=x;i;i=fa[i])  //找到点分树上x所在的所有点集 
        {  
  
            if(fa[i])  //如果有上一层的点分树 
            {  
                int d1=Distance(x,fa[i]);  
                last_ans+=Query(tree1[fa[i]],r[x]-d1);  
                last_ans-=Query(tree2[i],r[x]-d1);//计算上一层的点分树会带来多少收益  
                Insert(tree2[i],d1-r[x]);  //并在以fa[i]为根的树中i这棵子树的treap中插入关于x的信息 
            }  
  
            int d=Distance(x,i);  
            Insert(tree1[i],d-r[x]);  //在以i为根的这个点集的treap中插入关于x的信息 
              
        }  
  
        //前方野生替罪咩预警!  
  
        int to_rebuild=0;  
  
        for(i=x;fa[i];i=fa[i])  
            if( (double)tree1[i]->size/tree1[fa[i]]->size > ALPHA )  //如果一个节点(fa[i])的子树不满足替罪羊树的性质 (有一个子树太大了) 
                to_rebuild=fa[i];  //保证找到的是点分树上深度最浅的节点 ,由于如果重建了深度最浅的节点,就等于重建了它的整棵子树,所以它子树的节点自然不需要重建 
  
        if(to_rebuild)  
            Rebuild(to_rebuild);  
    }  
  
}  
int main()  
{  
    #ifndef ONLINE_JUDGE  
    freopen("3435.in","r",stdin);  
    freopen("3435.out","w",stdout);  
    #endif  
      
    srand(19980402);//我様は最強ね!にゃんははははは!~!  
    int i,x,y,z;  
    scanf("%*d%d",&n);  
    for(i=1;i<=n;i++)  
    {  
        scanf("%d%d%d",&x,&y,&z);  
        #ifdef ONLINE_JUDGE  
        x=x^(last_ans%1000000000);  
        #endif  
  
        Graph::Add(i,x,y);  
        Graph::Add(x,i,y);  
        Graph::ancestor[i][0]=x;  
        Graph::dpt[i]=Graph::dpt[x]+1;  
        Graph::dis[i]=Graph::dis[x]+y;  
        Graph::Build_LCA(i);  
        r[i]=z;  //更新原树(即以1为根,用来查询两点距离的树) 
  
        Dynamic_TDC::to[x].insert(i);  
        Dynamic_TDC::fa[i]=x;  
        Dynamic_TDC::Insert(i);  //在点分树上插入只有i这个点的点集 
  
        #ifdef ONLINE_JUDGE  
            printf("%lld\n",last_ans);  
        #else  
            printf("%I64d\n",last_ans);  
        #endif  
    }  
}  


  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值