[Vani有约会]雨天的尾巴 线段树合并

[Vani有约会]雨天的尾巴

LG传送门

线段树合并入门好题。

先别急着上线段树合并,考虑一下这题的暴力。一看就是树上差分,对于每一个节点统计每种救济粮的数量,再一遍dfs把差分的结果统计成答案。如果暴力统计,即对于每一个点开一个数组记录每种救济粮的数量,统计时再\(O(S)\)(设\(S\)为救济粮的种类数)地合并两个节点的信息,无论时间还是空间都是无法承受的,于是考虑优化。

容易想到对于每一个点开一棵值域线段树记录区间内救济粮最多的种类和相应的个数,但是这样还是会炸,所以不能用一般的线段树,考虑使用动态开点线段树。

所谓“动态开点”,其实就是不把一棵线段树建满,而只把我们需要的节点建出来。这样做就能够把空间复杂度降到\(O(mlogS)\),因为对于每一次救济粮的发放,最坏情况下只会开出4条长度为\(logS\)的链(树上差分,修改\(x\)\(y\)\(lca\)\(lca\)的父亲四处)。这样做的话,线段树上每个节点的儿子就不是\(k<<1\)\(k<<1|1\)了,需要单独记录。

其实把动态开点线段树建出来之后,线段树合并的想法就非常自然了,在这里先口胡一下,待会可以结合代码理解。对于两棵待合并的线段树上的记录相同值域的节点,考虑合并这两个节点的信息,如果某一个节点上没有信息,合并后的节点就直接继承另一个节点的信息,并且不用往下递归,否则就往下递归处理;直到统计的区间变成一个点,就合并两个点上的信息,回溯。其实和普通线段树的操作是非常相似的。事实上,这个算法的时间复杂度是\(O(mlogS)\),因为最坏情况下有\(O(m)\)条长度为\(O(logS)\)的链需要合并,所以最多只会有\(O(mlogS)\)个节点,而每个节点的信息只会被合并一次。

代码(没有刻意卡空间):

#include<cstdio>
#include<cctype>
#define R register
#define I inline
using namespace std;
const int S=100003,N=200003,M=8000003;
char buf[S],*p1,*p2;
I char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,S,stdin),p1==p2)?EOF:*p1++;}
I int rd(){
	R int f=0; R char c=gc();
	while(c<48||c>57) c=gc();
	while(c>47&&c<58) f=f*10+(c^48),c=gc();
	return f;
}
int h[S],s[N],g[N],d[S],t[S],p[S],q[S],r[S],v[S],f[S],c,e;
struct D{int u,c;
	friend int operator>(D x,D y){return x.c^y.c?x.c>y.c:x.u<y.u;}
	friend D operator+(D x,D y){return (D){x.u,x.c+y.c};}
};
struct T{int l,r; D v;}o[M];
I D max(D x,D y){return x>y?x:y;}
I void swp(int &x,int &y){x^=y,y^=x,x^=y;}
I void add(int x,int y){s[++c]=h[x],h[x]=c,g[c]=y;}
I void psu(int k){o[k].v=max(o[o[k].l].v,o[o[k].r].v);}
void ins(int &k,int l,int r,D v){if(!k) k=++e;
	if(l==r){o[k].v=v+o[k].v; return ;} R int m=l+r>>1;
	if(v.u<=m) ins(o[k].l,l,m,v); else ins(o[k].r,m+1,r,v); psu(k);
}
int mrg(int k,int t,int l,int r){
	if(!k) return t; if(!t) return k;
	if(l==r){o[k].v=o[k].v+o[t].v; return k;} R int m=l+r>>1;
	o[k].l=mrg(o[k].l,o[t].l,l,m),o[k].r=mrg(o[k].r,o[t].r,m+1,r),psu(k);
	return k;
}
void dfs1(int x,int f){p[x]=f,d[x]=d[f]+1,t[x]=1;
	for(R int i=h[x],y,m=0;i;i=s[i])
		if((y=g[i])^f){dfs1(y,x),t[x]+=t[y];
			if(t[y]>m) m=t[y],q[x]=y;
		}
}
void dfs2(int x,int t){r[x]=t;
	if(q[x]) dfs2(q[x],t);
	for(R int i=h[x],y;i;i=s[i])
		if(((y=g[i])^p[x])&&(y^q[x])) dfs2(y,y);
}
int lca(int x,int y){
	for(;r[x]^r[y];x=p[r[x]]) if(d[r[x]]<d[r[y]]) swp(x,y);
	return d[x]<d[y]?x:y;
}
void dfs(int x){
	for(R int i=h[x],y;i;i=s[i])
		if((y=g[i])^p[x]) dfs(y),v[x]=mrg(v[x],v[y],1,S);
	f[x]=o[v[x]].v.u;
}
int main(){
	R int n=rd(),m=rd(),i,x,y,z,u;
	for(i=1;i<n;++i) x=rd(),y=rd(),add(x,y),add(y,x);
	for(dfs1(1,0),dfs2(1,1),i=1;i<=m;++i){
		x=rd(),y=rd(),z=rd(),u=lca(x,y);
		ins(v[x],1,S,(D){z,1}),ins(v[y],1,S,(D){z,1}),ins(v[u],1,S,(D){z,-1});
		if(p[u]) ins(v[p[u]],1,S,(D){z,-1});
	}
	for(dfs(1),i=1;i<=n;++i) printf("%d\n",f[i]);
	return 0;
}

我不会告诉你我因为lca写挂调了几个小时

posted @ 2018-12-07 11:29  newbiechd  阅读(366)  评论(1编辑  收藏  举报