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的数据调试的绝望吧
写一下(还能想起来的)过程中犯的一些错误 作反例吧…。
- 点分树里单个节点的答案在拍扁的时候没清
- 光想着rebuild了 没更新重构范围之外 会被新加入点影响的答案
- 新加入的点到某一层(一个边界情况)分治根的距离忘记算 导致更新分治祖先答案时会出事
- 点分治找重心又又又写错了
- 输出ans之后直接模了1e9,并且觉得这样对之后无影响…
- 大数据出现奇怪的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);
}
}