哇这道题 恶心死我
首先要知道,树上差分一般解决的问题是关于树上的覆盖问题 然后遇到覆盖问题尽量不要打树剖(会慢很多)
关于此题 因为这道题覆盖的是 从\(x\)到\(y\)的点 所以我们在 \(x,y\)上打\(kind\) \(1\)的标记 然后在\(lca(x,y),fa(lca(x,y))\)上打\(kind\) \(-1\)的标记因为lca会被覆盖两次所以要打一个\(-1\)标记
那么这道题 我们可以对于每个\(kind\)都开一颗线段树啊。(理想很美好) 然而会爆空间
这个时候就需要考虑动态开点+线段树合并+垃圾回收
动态开点 就是如果要用当前点并且这个点的信息没有 那么就可以开这个点
垃圾回收 如果当前点不用了 就把他删了,把他的信息清零 然后把他放入栈中表示 当前点为空
线段树合并 将两颗线段树合并在一起,即传递了信息,也节省了空间 一举两得
然后这道题 相当于(但实际处理的时候用过的线段树就把它删了)对于每个节点 都开一颗线段树 存储区间\(l\)~\(r\)的信息 \(l\)~\(r\)表示的是这些一段区间 种类\(kind\)的最大值以及其\(cf\)值
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn=110000;
const int maxm=2000000;
const int M=1000000000;
int fa[maxn][30],first[maxn],next[maxn<<2],to[maxn<<2];
int val[maxn<<2],kind[maxn<<2],cf[maxm],kcf[maxm];
int root[maxm],ans[maxn];
int dep[maxn];
int size[maxn],rt_size[maxn];
int n,m,tot;
int rs[maxm],ls[maxm];
int rub[maxm],top,cnt=0;
queue<int> q;
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
ch=getchar();
while(ch>='0' && ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
void add(int x,int y)
{
tot++;
next[tot]=first[x];
first[x]=tot;
to[tot]=y;
}
void bfs()//这里只能用bfs 用dfs要爆栈
{
int qq[maxn],head,tail;
head=1;tail=0;
qq[++tail]=1;
while(head<=tail)
{
int x=qq[head];head++;
size[x]=1;dep[x]=dep[fa[x][0]]+1;
for(int i=0;i<=20;i++)
fa[x][i+1]=fa[fa[x][i]][i];
for(int i=first[x];i;i=next[i])
{
int y=to[i];
if(y==fa[x][0]) continue;
fa[y][0]=x;
qq[++tail]=y;
}
}
while(tail)//利用bfs的性质 将非叶节点 更新 然后将叶节点 入队
{
size[fa[qq[tail]][0]]+=size[qq[tail]];
if(size[qq[tail]]==1)
q.push(qq[tail]);
tail--;
}
}
int LCA(int x,int y)//LCA
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=21;i>=0;i--)
{
if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(y==x) return x;
}
for(int i=21;i>=0;i--)
{
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
void insert(int x,int k,int wow)//相当于将x与其信息连边
{
tot++;
next[tot]=first[x];
first[x]=tot;
val[tot]=wow;
kind[tot]=k;
}
int newnode()
{
if(top) return rub[top--];
return ++cnt;
}
void push_up(int node)//用儿子节点更新当前节点
{
if(cf[ls[node]]>=cf[rs[node]])//因为要求当前节点的覆盖尽量大的kind
{
cf[node]=cf[ls[node]];
kcf[node]=kcf[ls[node]];
}
else
{
cf[node]=cf[rs[node]];
kcf[node]=kcf[rs[node]];
}
}
void update(int l,int r,int &node,int k,int pri)
{
if(!node) node=newnode();//如果当前节点为空 新建一个节点
if(l==r){
cf[node]+=pri;//增加当前 pri 值
kcf[node]=l;// 将当前节点kind 赋值
}
else{
int mid=(l+r)>>1;
if(k<=mid){
update(l,mid,ls[node],k,pri);
}
else{
update(mid+1,r,rs[node],k,pri);
}
push_up(node);//一定要打push_up
}
if(!cf[node])//如果当前差分值为0 那么说明当前节点不被任何kind覆盖
kcf[node]=0;//那么当前节点的kind=0
}
void del(int x)//删除
{
rub[++top]=x;//将当前点加入栈中
ls[x]=0;//将当前点信息清零
rs[x]=0;
cf[x]=0;
}
int merge(int l,int r,int nodeone,int nodetwo)
{
if(!nodeone || !nodetwo) return nodeone+nodetwo;//线段树合并如果对应点为某一个为0 返回另一个点的node
int node=newnode();
if(l==r)
{
cf[node]=cf[nodeone]+cf[nodetwo];//更新合并节点cf值 因为cf值要从子节点更新上来
kcf[node]=l;//合并节点的 kind 因为当前合并点区间l==r 所以可以更新
}
else
{
int mid=(l+r)>>1;
ls[node]=merge(l,mid,ls[nodeone],ls[nodetwo]);
rs[node]=merge(mid+1,r,rs[nodeone],rs[nodetwo]);
push_up(node);//此处应该push_up fuck!!!!!
}
del(nodeone);del(nodetwo);//删除 因为这两个节点已经被新的节点代替
return node;
}
int main()
{
n=read();m=read();
for(int i=1;i<=n-1;i++)
{
int x,y;
x=read();y=read();
add(x,y);add(y,x);
}
bfs();
memset(first,0,sizeof(first));
tot=0;
for(int i=1;i<=m;i++)
{
int x,y,z,lca;
x=read();y=read();z=read();
lca=LCA(x,y);
insert(x,z,1);//这里的insert表示的是 存储与x有关的信息
insert(y,z,1);
insert(lca,z,-1);
insert(fa[lca][0],z,-1);
}
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=first[x];i;i=next[i])//遍历与x有关的信息
update(1,M,root[x],kind[i],val[i]);//更新线段树信息
ans[x]=kcf[root[x]];//相当于x 对应的线段树的根节点
root[fa[x][0]]=merge(1,M,root[x],root[fa[x][0]]);//合并他和他的父亲 即更新了信息 又节省了空间
rt_size[fa[x][0]]+=size[x];//这两行相当于一个判重 每个点只会入队一次
if(rt_size[fa[x][0]]+1==size[fa[x][0]])
q.push(fa[x][0]);
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}