MnZn 的树上问题总结
0.前言
几个月前学的东西现在已经不会了QAQ
1./树上启发式合并
可以优雅而较为高效地离线处理子树内的信息。可以先考虑在一棵树内怎样统计答案,然后推广到整棵树上。
路径信息一般是以子树的根作为路径的 lca 考虑
时间复杂度证明:
树上一条链的重链数量是 \(log\) 级的。在 dsu on tree 的过程中,每个点在重链上会插入、删除各一次,所以复杂度是 \(O(n\log n)\) ,但因为使用 map 统计信息所以可能常数较大
伪码
void solve(int x,int flag)
{
for (枚举轻儿子) solve(v,0)
solve(重儿子,1)//此时重子树的信息未被删去
for(枚举轻儿子)
{
for (枚举轻儿子子树节点) calc()//计算贡献
for (枚举轻儿子子树节点) add()//计入 注意要计算完贡献再加点 以防子树内部自己匹配
}
add(),calc()//必要时再计入 x 节点、计算 x 节点贡献
if (!flag) del()//若是轻儿子,删去贡献
//其实最后的“删去”也可以直接mp.clear(),但这里一个个点删和整体二分里一个个点删的道理一样
}
CF600E Lomsat gelral
子树数颜色。用 map 维护子树内部每个颜色的出现次数,并额外用 \(ans\) 记录最多次数的颜色的编号即可
P4149 [IOI 2011] Race
给定路径权值和求最短路径长度。在 map 中以到根的距离作下标,维护相应距离的点的深度最小值(固定 lca 与一个端点的情况下,另一个路径端点深度越小路径长度越小),套用距离公式计算即可
P1600 [NOIP 2016 提高组] 天天爱跑步
给出若干路径求对每个点的贡献。将每条路径分为两半并求出相应的深度与贡献的关系,在此题中需用两个 map 维护起点/终点的信息。因为计算贡献需不重不漏,一条路径只能对一个点贡献一次,所以可以使用树上差分的插入方法:给起点插入次“询问”,终点插入次“询问”,lca 处减去次“询问”,lca 的父节点处减去最后的询问
2.点分治
适合处理大规模的树上路径统计问题。
对于一些路径问题,暴力是分别枚举 \(n\) 个路径中经过的点 \(u\) 并以 \(u\) 为根跑一遍树并统计答案。显然,这样一条路径会被它上面的所有点分别统计一次,所以复杂度直接爆棚。
考虑在 dfs 的过程中,若已经统计过了经过点 \(u\) 的路径,那么递归时就不用保存 \(u\) 子树内的信息。于是现在就有一个找根——dfs——统计答案——递归子树的过程。现在就是要确定根的选取使得时间复杂度正确。每次使得根为子树的重心是正确的
时间复杂度证明:
因为每次都以重心为根,分到子树内部时子树大小至少减少一半,所以点分治的递归深度是 \(log\) 级的。每个点会被分治过程中的 \(log\) 个子树统计到,所以总复杂度是 \(O(n\log n)\)
伪码
void get_rt(int x,int _fa,int tol)//根据性质“最大子树最小的点为重心”找根
{
siz[x]=1; mx[x]=0;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]||_fa==v) continue;
get_rt(v,x,tol);
mx[x]=max(mx[x],siz[v]); siz[x]+=siz[v];
}
mx[x]=max(mx[x],tol-siz[x]);
if (!rt||mx[x]<mx[rt]) rt=x;
}
void dfs(int x,int _fa) { //根据题意递归子树即可 }
void solve(int x,int sz)
{
vis[x]=1;//经过x的所有路径都统计过了
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]) continue;
dfs(v,x); calc(v);//递归子树并统计答案
}
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]) continue;//已经作为分治中心过了
int ea=(siz[v]>siz[x]?sz-siz[x]:siz[v]);//以上写法的siz并非以x为根的siz所以需特判
rt=0; get_rt(v,x,ea); solve(rt,ea);
}
}
P3806 【模板】点分治 1
判断是否存在给定长度的路径。每个点都 get_dis 一下,再用双指针暴力判断即可
P4886 快递员
给出终点要求确定起点使得距离和最小。首先肯定和重心有关。考虑在什么样的情况下转移重心可以使得方案更优,并转移。因为在子树中找重心最多 \(log\) 次,所以总复杂度是 \(O(n\ log\ n)\)。尽管这样的复杂度很美丽,但还是不能不顾一切转移到底(这题卡常似乎无前途),当目前方案无法最优时直接输出即可。
P3714 [BJOI2017] 树的难题
考虑从 \(rt\) 出发的若干条链,记链的颜色 \(col\) 为边 \((rt,链顶)\) 的颜色,记其权值 \(val\) 为链顶到链底的颜色序列上每个同颜色段的颜色权值之和。对于颜色相同链,它们拼接到一起的权值就是 \(val_i+val_j-c\),反之就是 \(val_i+val_j\)。因为两种情况权值的计算不同,所以需要分别考虑
在点分治递归子树的过程中,记 \(tmp_i\) 为深度为 \(i\) 的最大权值,显然一棵子树的所有链的颜色相同,所以可以统计计算。因为要求路径长度为 \([l,r]\),所以深度 \(i\) 的可匹配深度范围为 \([l-i,r-i]\)。现在问题转为了区间最大值,显然可以用线段树分别维护颜色相同/不同时每个深度的权值最大值,用其求出与 \(tmp_i\) 的权值更新答案。
此时要求遍历的子树颜色是连续的,遍历时给子节点按颜色排序即可,复杂度 \(O(n\log^2 n)\)
单调队列看上去也非常对,但是其滑动窗口的长度打底为 \(O(r-l)\),数据可以随便卡(吧)
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
const int inf=0x7f7f7f7f;
int n,m,l,r;
int a[N];
struct Edge{ int nxt,val; };
vector <Edge> tr[N];
int rt,vis[N],siz[N],mx[N];
struct Segment_Tree
{
struct node{
int l,r;
int mx,tag;
}tr[N<<2];
inline void push_up(int id) { tr[id].mx=max(tr[id<<1].mx,tr[id<<1|1].mx); }
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r,tr[id].mx=-inf;
if (l==r) return ;
int mid=(l+r)>>1;
build(id<<1,l,mid),build(id<<1|1,mid+1,r);
}
void rbuild(int id,int l,int r)
{
tr[id].mx=-inf;
if (tr[id].l==tr[id].r) return ;
int mid=(tr[id].l+tr[id].r)>>1;
if (mid>=l) rbuild(id<<1,l,r);
if (mid+1<=r) rbuild(id<<1|1,l,r);
}
void update(int id,int pos,int k)
{
if (tr[id].l==pos&&tr[id].r==pos) { tr[id].mx=max(tr[id].mx,k); return ; }
int mid=(tr[id].l+tr[id].r)>>1;
if (pos<=mid) update(id<<1,pos,k);
else update(id<<1|1,pos,k); push_up(id);
}
int query(int id,int l,int r)
{
if (tr[id].l>=l&&tr[id].r<=r) return tr[id].mx;
if (tr[id].l==tr[id].r) return -inf;
int mid=(tr[id].l+tr[id].r)>>1,res=-inf;
if (mid+1<=r) res=query(id<<1|1,l,r);
if (mid>=l) res=max(res,query(id<<1,l,r));
return res;
}
}Tr1,Tr2;
void get_rt(int x,int _fa,int tol)
{
siz[x]=1; mx[x]=0;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]||_fa==v) continue;
get_rt(v,x,tol);
mx[x]=max(mx[x],siz[v]); siz[x]+=siz[v];
}
mx[x]=max(mx[x],tol-siz[x]);
if (!rt||mx[x]<mx[rt]) rt=x;
}
int val2[N],ans=-inf;
int tmp[N],max_dep;
void dfs(int x,int _fa,int sum,int col,int dep)
{
tmp[dep]=max(tmp[dep],sum);
if (dep>=l) ans=max(ans,sum);
max_dep=max(max_dep,dep);
if (dep==r) return ;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]||v==_fa) continue;
dfs(v,x,sum+(col==tr[x][i].val?0:a[tr[x][i].val]),tr[x][i].val,dep+1);
}
}
inline bool cmp(Edge x,Edge y) { return x.val<y.val; }
int q[N],hd,tl;
inline void calc(int col)
{
int c=a[col];
for (int i=max_dep;i>=1;i--) ans=max({ans,tmp[i]+Tr1.query(1,l-i,r-i),tmp[i]+Tr2.query(1,l-i,r-i)-c});
for (int i=1;i<=max_dep;i++) { if (tmp[i]>val2[i]) { val2[i]=tmp[i]; Tr2.update(1,i,tmp[i]); } tmp[i]=-inf; }
}
void solve(int x,int sz)
{
sort(tr[x].begin(),tr[x].end(),cmp);
vis[x]=1; int pre=-1,mx=0;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]) continue;
if (tr[x][i].val!=pre&&pre!=-1)
{
int j;
for (j=1;val2[j]!=-inf;j++) { Tr1.update(1,j,val2[j]); val2[j]=-inf; }
Tr2.rbuild(1,1,j-1);
}
max_dep=0; dfs(v,x,a[tr[x][i].val],tr[x][i].val,1);
calc(tr[x][i].val); pre=tr[x][i].val; mx=max(mx,max_dep);
}
int i;
for (i=1;val2[i]!=-inf;i++) val2[i]=-inf;
Tr2.rbuild(1,1,i); Tr1.rbuild(1,1,mx);
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]) continue;
int ea=(siz[v]>siz[x]?sz-siz[x]:siz[v]);
rt=0; get_rt(v,x,ea); solve(rt,ea);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>l>>r;
for (int i=1;i<=m;i++) cin>>a[i];
for (int i=1,u,v,c;i<n;i++)
{
cin>>u>>v>>c;
tr[u].push_back({v,c});
tr[v].push_back({u,c});
}
Tr1.build(1,1,n); Tr2.build(1,1,n);
for (int i=1;i<=n;i++) val2[i]=tmp[i]=-inf;
get_rt(1,0,n); solve(rt,n); cout<<ans; return 0;
}
P7215 [JOISC 2020] 首都
先考虑在树中找到包含 \(x\) 的合法联通块(合法联通块是指它包含的所有颜色的所有结点都在该联通块内)。可以用队列维护联通块内的颜色,现将 \(x\) 的颜色进队,后枚举该颜色的所有结点 \(v\),因为要求颜色连通所以要求路径 \((x,v)\) 上所有点的颜色也要计入答案,所以每次再把 \(fa_v\) 的颜色插入队列即可
显然不能枚举所有的 \(x\) 统计答案,考虑点分治。点分治的最大问题莫过于统计子树时某颜色的某节点可能不在当前的子树内,如果还统计到这个点复杂度就不对了。考虑一定存在上层递归中心使得该颜色的所有结点都在子树内,即该颜色被上层递归中心计算过,此次答案一定不会更优,所以直接跳出即可。复杂度为朴素的 \(O(n\log n)\)
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,k;
vector <int> tr[N];
vector <int> col[N];
int rt,siz[N],mx[N];
int vis[N],ans=N;
int q[N],hd,tl,inq[N];
int fa[N],c[N];
void get_rt(int x,int _fa,int tol)
{
siz[x]=1; mx[x]=0;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (_fa==(v=tr[x][i])||vis[v]) continue;
get_rt(v,x,tol);
siz[x]+=siz[v]; mx[x]=max(mx[x],siz[v]);
}
mx[x]=max(mx[x],tol-siz[x]);
if (!rt||mx[x]<mx[rt]) rt=x;
}
void dfs(int x,int _fa,int f)
{
fa[x]=_fa*f; int _size=tr[x].size(),v;
for (int i=0;i<_size;i++) if (!vis[v=tr[x][i]]&&v!=_fa) dfs(v,x,f);
}
bool add(int x)
{
int _size=col[x].size(),v,res=0;
for (int i=0;i<_size;i++)
{
if ((v=col[x][i])==rt) continue;
if (!(v=fa[v])) return false;
if (inq[c[v]]) continue;
inq[c[v]]=1; q[++tl]=c[v];
}
return true;
}
void clr() { for (int i=1;i<=tl;i++) inq[q[i]]=0; }
void calc(int s)
{
q[hd=tl=1]=c[s]; inq[c[s]]=1;
while (hd<=tl)
{
int c=q[hd++];
if (!add(c)) { clr(); return ; }
}
ans=min(ans,tl-1); clr();
}
void solve(int x,int tol)
{
vis[x]=1; int _size=tr[x].size(),v,ea=0;
for (int i=0;i<_size;i++) if (!vis[v=tr[x][i]]) dfs(v,x,1);
calc(x);
for (int i=0;i<_size;i++) if (!vis[v=tr[x][i]]) dfs(v,x,0);
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i]]) continue;
ea=(siz[v]>siz[x]?tol-siz[x]:siz[v]);
rt=0; get_rt(v,x,ea); solve(rt,tol);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k; int u,v;
for (int i=1;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v);
tr[v].push_back(u);
}
for (int i=1;i<=n;i++) { cin>>c[i]; col[c[i]].push_back(i); }
get_rt(1,0,n); solve(rt,n); cout<<ans; return 0;
}
P5311 [Ynoi2011] 成都七中
是!毒!瘤!
对于一次询问 \((l,r,x)\),若保留 \([l,r]\) 的所有点后点 \(u\) 在 \(x\) 所在的联通块内,路径 \((x,u)\) 上的所有点 \(v\) 一定满足 \(\forall v\in[l,r]\);转化一下,记 \(max(x,u)\) 为路径 \((x,u)\) 上点编号的最大值,\(min(x,u)\) 为最小值,两点连通当且仅当 \(min(x,u)\geqslant l,max(x,u)\leqslant r\)。那么可以在跑一遍 dfs 后将问题转化为二维数点问题,相同颜色考虑只添加 \(min(x,u)\) 较大的点,显然这不会使得答案更劣。
因为需要跑“联通块”,考虑点分治。为了正确性,分治中心 \(rt\) 需要处于询问 \((l,r,x)\) 中 \(x\) 所在的联通块且该子树包含联通块的所有点。首先可以证明一定是存在这样的一个分治中心的(可以考虑从点分治的叶子结点往上跳的过程),且递归深度最浅的与 \(x\) 连通的分治中心一定满足上述条件(仍是考虑点分树跳 \(fa\) 的过程),那么每次分治扫一遍子树中的所有询问并判断分治中心与询问中的 \(x\) 是否连通再判断该询问是否跑过就可以得到该次分治的所有查询操作。然后二维数点就好了,复杂度 \(O(n\log^2 n)\)
感觉总体思路还是挺顺利的 [赞]
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m,c[N];
vector <int> tr[N];
struct node { int r,l,id; };
vector <node> q[N],add,tmp;
int rt,siz[N],mx[N],vis[N];
int ans[N],t[N];
struct BIT
{
int tr[N];
inline int lowbit(int x) { return x&-x; }
inline void add(int x,int y) { if (!x) return ; while (x<=n) { tr[x]+=y; x+=lowbit(x); } }
inline void clr(int x) { if (!x) return ; while (x<=n) { tr[x]=0; x+=lowbit(x); } }
inline int sum(int x) { int res=0; while (x) { res+=tr[x]; x-=lowbit(x); } return res; }
}bt;
void get_rt(int x,int _fa,int tol)
{
siz[x]=1; mx[x]=0;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i]]||v==_fa) continue;
get_rt(v,x,tol);
siz[x]+=siz[v]; mx[x]=max(mx[x],siz[v]);
}
mx[x]=max(mx[x],tol-siz[x]);
if (!rt||mx[x]<mx[rt]) rt=x;
}
void dfs(int x,int _fa,int mx,int mn)
{
add.push_back({mx,mn,c[x]});
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++) if (!vis[v=tr[x][i]]&&v!=_fa) dfs(v,x,max(mx,v),min(mn,v));
_size=q[x].size();
for (int i=0;i<_size;i++)
if (!t[q[x][i].id]&&mn>=q[x][i].l&&mx<=q[x][i].r) { tmp.push_back(q[x][i]); t[q[x][i].id]=1; }
}
bool cmp(node x,node y) { return x.r<y.r; }
int pre[N];
void calc()
{
sort(add.begin(),add.end(),cmp);
sort(tmp.begin(),tmp.end(),cmp);
int j=0,_size1=add.size(),_size=tmp.size();
for (int i=0;i<_size;i++)
{
while (j<_size1&&add[j].r<=tmp[i].r)
{
if (add[j].l>pre[add[j].id])
{
bt.add(pre[add[j].id],-1);
bt.add(add[j].l,1); pre[add[j].id]=add[j].l;
}
j++;
}
ans[tmp[i].id]=bt.sum(n)-bt.sum(tmp[i].l-1);
}
for (int i=0;i<j;i++) { bt.clr(pre[add[i].id]); pre[add[i].id]=0; }
}
void solve(int x,int tol)
{
vis[x]=1; dfs(x,0,x,x);
calc(); add.clear(),tmp.clear();
int _size=tr[x].size(),v,ea;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i]]) continue;
ea=(siz[v]>siz[x]?tol-siz[x]:siz[v]);
rt=0; get_rt(v,x,ea); solve(rt,ea);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m; int u,v,x;
for (int i=1;i<=n;i++) cin>>c[i];
for (int i=1;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v);
tr[v].push_back(u);
}
for (int i=1;i<=m;i++)
{
cin>>u>>v>>x;
q[x].push_back({v,u,i});
}
get_rt(1,0,n); solve(rt,n);
for (int i=1;i<=m;i++) cout<<ans[i]<<"\n"; return 0;
}
3./点分树(动态点分治)
若带修查询/强制在线,静态点分治就不够优秀了。考虑在点分治的递归过程中,一个点最多被 \(log\) 个子树包含计算,修改也只会影响这 \(log\) 个子树。所以可以根据点分治过程中的重心建树,在点分树中,一个节点的子节点是 点分治过程中该节点为重心的树的子树的重心,其深度和点分治的递归深度一样是 \(log\) 级的。
一般点分树上每个节点会用一个数据结构维护它的树的信息,每次修改只需要修改这个节点在点分树上的各级祖先维护的信息(所以点分树只需要记录每个点的父节点,不需要建完树)。因为这些树存在包含关系,为了统计得不重不漏,还需要再用一个数据结构维护它的树和它在点分树上的父节点的信息(一般不能不建第二个,因为它的树和它点分树上的父节点可能八竿子打不着)。每次修改是 $log\times $数据结构复杂度
P6329 【模板】点分树 | 震波
求给出距离内的价值和。在每个节点处,用一个线段树维护每个距离的价值和,然后直接查查完了(需再建一棵线段树去重)
P2056 [ZJOI2007] 捉迷藏
求同色点最长路径。用堆维护最大值&次大值,然后直接算算完了
trick:在堆内可以实现“修改”。将修改掰成“删除”和“插入”,插入好说,删除操作需额外开个堆记录删除的元素,每次查询前先把位于堆顶需删除的元素 pop 掉就好了
P3920 [WC2014] 紫荆花之恋
毒瘤。
先给式子拆开。设 \(l\) 为 \(lca(u,v)\),那么给式子变形就有
容易想到在 \(l\) 处统计答案,即上点分治;因为这题还有动态插点,所以考虑点分树,每插入一个点就不断跳它在点分树上的父亲并计算其子树的贡献、再挖掉 \(x\) 方向的贡献。贡献的计算需要上平衡树(由于强制在线完全不能离散化所以不能上线段树/树状数组),每次查询 \(\leqslant r_v-dis_v\) 的数量并插入 \(dis_v-r_v\)
但是这题还强制在线,不知道树的形态,也就是说刚开始不能建出完整平衡的点分树。可以先每次都暴力插入,将原树上的父节点看作点分树上的父节点;但这样插入一定次数后点分树肯定会不平衡,所以需要每次插完点后从根往下判断当前子树是否平衡,不平衡的话暴力重构。显然每插入一个点最多重构一棵树即可
在重构点分树时,记录贡献的数据结构肯定也要重构。若是写平衡树,应该需要啥啥回收(不然会重构次数一多空间就会爆),我不会,所以用的 basic_string 根号重构。即:记录一个大的 basic_string,其中元素有序,查询只需要upper_bound;再记录一个小的 basicstirng,其中元素无序但其 size 不超过根号。当小的那个个数超过根号再将其与大的排序合并即可
史的地方就在于重构点分树和贡献的维护上,其他的和普通点分树也没啥区别,复杂度 \(O(能过)\)
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const int B=400;
const int MOD=1e9;
int T,n,r[N],ans;
struct Edge { int nxt,val; };
vector <Edge> tr[N];
basic_string <int> fa[N],son[N],dis[N],tmp;//动态维护点分树上的祖先、到祖先的距离和子树节点
struct node
{
basic_string <int> a,b,tmp;
void clr() { a=b=tmp={}; }
void insert(int x)
{
b.push_back(x); if (b.size()<=B) return ;
sort(b.begin(),b.end()); tmp.resize(a.size()+b.size());//merge()函数要求有序且有足够空间存储
merge(a.begin(),a.end(),b.begin(),b.end(),tmp.begin());//有序合并俩
swap(tmp,a),b={};
}
int get(int x)
{
int res=upper_bound(a.begin(),a.end(),x)-a.begin();
for (int i=b.size()-1;i>=0;i--) res+=(b[i]<=x); return res;
}
}t1[N],t2[N];
int vis[N],ok[N],rt,mx[N],siz[N],pre[N],tol;
int query(int x)//和普通点分树没啥两样
{
int res=0,pre=0,now;
for (int i=fa[x].size()-1;i>=0;i--)
{
now=fa[x][i]; son[now].push_back(x);
if (pre) res-=t2[pre].get(r[x]-dis[x][i]);
res+=t1[now].get(r[x]-dis[x][i]);
if (pre) t2[pre].insert(dis[x][i]-r[x]);
t1[now].insert(dis[x][i]-r[x]); pre=now;
}
return res;
}
void get_rt(int x,int _fa,int tol)
{
siz[x]=1,mx[x]=0; int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (_fa==(v=tr[x][i].nxt)||vis[v]||!ok[v]) continue;
get_rt(v,x,tol); siz[x]+=siz[v],mx[x]=max(mx[x],siz[v]);
}
mx[x]=max(mx[x],tol-siz[x]); if (!rt||mx[x]<mx[rt]) rt=x;
}
void dfs(int x,int _fa,int _dis)//在过程中维护它的fa,son,dis集合
{
fa[x].push_back(rt),dis[x].push_back(_dis);
t1[rt].insert(_dis-r[x]),son[rt].push_back(x);
if (rt!=tol) t2[rt].insert(pre[x]-r[x]);
int _size=tr[x].size(),v; pre[x]=_dis;
for (int i=0;i<_size;i++) if (!vis[v=tr[x][i].nxt]&&v!=_fa&&ok[v]) dfs(v,x,_dis+tr[x][i].val);
}
void solve(int x,int tol)
{
vis[x]=1; dfs(x,0,0);
int _size=tr[x].size(),v,ea;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]||!ok[v]) continue;
ea=(siz[v]>siz[x]?tol-siz[x]:siz[v]);
rt=0,get_rt(v,x,ea),solve(rt,ea);
}
vis[x]=0;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T>>n; int _fa,_dis; cin>>_fa>>_dis>>r[1]; cout<<"0\n";
fa[1].push_back(1); son[1].push_back(1); dis[1].push_back(0);
t1[1].insert(-r[1]);
for (int i=2,_size;i<=n;i++)
{
cin>>_fa>>_dis>>r[i]; _fa^=(ans%MOD); tr[_fa].push_back({i,_dis}),tr[i].push_back({_fa,_dis});
_size=fa[_fa].size(),fa[i]=fa[_fa],fa[i].push_back(i),dis[i]=dis[_fa],dis[i].push_back(0);
for (int j=0;j<_size;j++) dis[i][j]+=_dis; ans+=query(i); cout<<ans<<"\n";
_size=fa[i].size();
for (int j=1;j<_size;j++)
{
if (son[fa[i][j]].size()*1.0<=0.7*son[fa[i][j-1]].size()) continue;
int now=fa[i][j-1],ea;
for (int k=ea=son[now].size()-1;k>=0;k--)
{
int u=son[now][k];
ok[u]=1; son[u]={},t1[u].clr(),t2[u].clr();
for (int p=fa[u].size()-1;p>=0&&fa[u][p]!=now;p--) fa[u].pop_back(),dis[u].pop_back();
fa[u].pop_back(),dis[u].pop_back();
if (dis[u].size()) pre[u]=dis[u][dis[u].size()-1]; tmp+=u;
}
tol=(fa[now].size()?fa[now][fa[now].size()-1]:-1);
rt=0,get_rt(now,0,ea+1),now=rt,solve(rt,ea);
for (int k=tmp.size()-1;k>=0;k--) ok[tmp[k]]=0; tmp={};break;
}
}
return 0;
}

浙公网安备 33010602011771号