题目大意:给定一棵树,每次添加一个节点并询问当前有多少点对满足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
}
}