uoj#55./bzoj3435 【WC2014】紫荆花之恋 //替罪羊式重构点分树

uoj#55. 【WC2014】紫荆花之恋


题意

N(<=1e5)次操作,第i次操作会把第i个点挂到当前的树上。
点有点权Ri,边有边权Ci。求每次操作后满足Ri+Rj>=dis(i,j)的点对(i,j)的个数。
强制在线。


题解

//想知道那个非常非常短的写法是什么神奇的操作QAQ
//我好像只会比较trivial的做法…
进入正题。
树上路径计数?哇这不是点分治模板题嘛,套个数据结构就好了。
如果树的形态不确定呢?
我们可以用替罪羊树的方法维护点分树,在它长歪了的时候拍扁重构。
整体复杂度是点分的log * 平衡树的log * 重构的log 也即O(nlog^3n)(空间是O(nlogn)的)
说起来简单 写写试试?
注:内层的数据结构写了SGT…这样就可以一次练一道半份的SGT了;w;
又注:时间复杂度证不明白 所以alpha是瞎设的 交了之后看了一下其他题解 发现自己这个好像十分不对劲x
又注:这种东西还是半夜去评测比较好,不然(尤其写T的时候)会掉rp。


注意事项

名不虚传。还是比较毒瘤的。
调起来比写起来麻烦多了…。感受拿着一组N>1e4的数据调试的绝望吧
写一下(还能想起来的)过程中犯的一些错误 作反例吧…。

  1. 点分树里单个节点的答案在拍扁的时候没清
  2. 光想着rebuild了 没更新重构范围之外 会被新加入点影响的答案
  3. 新加入的点到某一层(一个边界情况)分治根的距离忘记算 导致更新分治祖先答案时会出事
  4. 点分治找重心又又又写错了
  5. 输出ans之后直接模了1e9,并且觉得这样对之后无影响…
  6. 大数据出现奇怪的RE 删了内层插入函数一个不必要的参数以后忽然好了

代码

#include<bits/stdc++.h>
#define N 100005
#define V 4000000
#define New (bc?bin[--bc]:++tot)
using namespace std;
typedef long long ll;
ll ans,ss[N];
int A,C,n,a[N],siz[N],mxs[N],f[N],dp[N],rt[N],rtt[N],
dep[N][36],to[N<<1],hd[N<<1],len[N<<1],lk[N],cnt,pre,
bin[V],b[N],bc,c[V][2],val[V],sz[V],t0,t1,tit,tot;
//第三行的数组是内层的 上面的都是外层相关
//我知道这样起变量名特别糟 请不要骂我QAQ
bool st[N];//用于不规范的sgt写法(雾
char ch;
inline void rd(int &x)
{
    x=0;
    do ch=getchar();
    while(ch>'9'||ch<'0');
    do x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    while(ch<='9'&&ch>='0');
}
void Ins(int &k,int v)
{
    if(k)st[t0++]=v>val[k],
    sz[k]++,Ins(c[k][v>val[k]],v);
    else k=New,val[k]=v,sz[k]=1;
}
void Del(int &k)
{if(k)sz[bin[bc++]=k]=0,Del(c[k][0]),Del(c[k][1]),k=0;}
void Flatten(int k)
{if(k)Flatten(c[k][0]),b[++tit]=k,Flatten(c[k][1]);}
int Rebuild(int l,int r)
{
    register int mid=l+r>>1,k;
    sz[k=b[mid]]=r-l+1;
    c[k][0]=l<mid?Rebuild(l,mid-1):0;
    c[k][1]=mid<r?Rebuild(mid+1,r):0;
    return k;
}
//首字母大写的函数是内层sgt的
void check(int &k)
{
    if(sz[k]*7<sz[c[k][st[t1]]]*10)
    tit=0,Flatten(k),
    k=Rebuild(1,tit);
    else if(t1<t0)
    check(c[k][st[t1++]]);
}
inline void ins(int &k,int v)
{t0=0,Ins(k,v);t1=0,check(k);}
inline int Qry(int k,int v)
{
    register int ret=0;
    while(k)
    if(val[k]<v)k=c[k][1];
    else ret+=sz[c[k][1]]+1,k=c[k][0];
    return ret;
}
void rebuild(int x,int d)
{
    dp[x]=0,siz[x]=1,mxs[x]=0;
    ans-=ss[x],ss[x]=0;
    Del(rt[x]),Del(rtt[x]);
    for(int k,i=lk[x];i;i=hd[i])
    if(dp[k=to[i]]>=d)
    rebuild(k,d),siz[x]+=siz[k];
}//找到要rebuild的点分树部分 顺手清个零
int getr(int x,int y,int nn)
{
    for(int k,i=lk[x];i;i=hd[i])
    if(((k=to[i])^y)&&siz[k]>nn&&!dp[k])
    return getr(k,x,nn);
    return x;
}
void dfs(int x,int y,int fr)
{
    siz[x]=1;
    ss[fr]+=Qry(rt[fr],dep[x][dp[fr]]-a[x]);
    ins(rt[fr],a[x]-dep[x][dp[fr]]);
    if(dp[fr]>1)
    ss[fr]-=Qry(rtt[fr],dep[x][dp[fr]-1]-a[x]),
    ins(rtt[fr],a[x]-dep[x][dp[fr]-1]);
    for(int k,i=lk[x];i;i=hd[i])
    if(((k=to[i])^y)&&!dp[k])
    dep[k][dp[fr]]=dep[x][dp[fr]]+len[i],
    dfs(k,x,fr),siz[x]+=siz[k];
}
void build(int x,int fa)
{
    dp[x=getr(x,x,siz[x]>>1)]=dp[fa]+1;
    f[x]=fa,dep[x][dp[x]]=0;
    dfs(x,x,x);ans+=ss[x];
    for(int k,i=lk[x];i;i=hd[i])
    if(!dp[k=to[i]])build(k,x),
    mxs[x]=mxs[x]<siz[k]?siz[k]:mxs[x];
}//上面这一段就是点分治的build了
inline void add(int u,int v)
{to[++cnt]=v,hd[cnt]=lk[u],len[cnt]=C,lk[u]=cnt;}
int j;
int main()
{
    rd(n),rd(n);
    for(int i=1;i<=n;i++)
    {
        rd(A),rd(C),rd(a[i]);
        A^=(ans%1000000000);
        if(i>1)add(A,i),add(i,A);
        dp[i]=dp[f[i]=A]+1;
        pre=0;
        for(j=i;j;j=f[j])
        {
            siz[j]++;
            if(f[j]&&mxs[f[j]]<siz[j])
            mxs[f[j]]=siz[j],
            pre=(siz[f[j]]+1)*7<siz[j]*10?f[j]:pre;
        }//判一下从哪里开始重构
        if(pre)
        rebuild(pre,dp[pre]),j=f[pre],
        dep[i][dp[j]]=dep[A][dp[j]]+C,
        build(pre,j);
        else j=i;
        for(;j;j=f[j])
        {
            pre=Qry(rt[j],dep[i][dp[j]]-a[i]);
            ss[j]+=pre,ans+=pre;
            ins(rt[j],a[i]-dep[i][dp[j]]);
            if(f[j])
            pre=Qry(rtt[j],(dep[i][dp[j]-1]=
            dep[A][dp[j]-1]+C)-a[i]),
            ss[j]-=pre,ans-=pre,
            ins(rtt[j],a[i]-dep[i][dp[j]-1]);
        }//向上更新答案
        printf("%lld\n",ans);
    }
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值