把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4538】[HNOI2016] 网络(整体二分+树状数组)

点此看题面

大致题意: 给定一棵无根树,三种操作:建立一个权值\(x\)的经过两点树上路径的任务;取消之前的一个任务;询问所有不经过树上给定点的任务中最大的权值。

整体二分

看到这道题,我们首先应该想到整体二分。(实际上我这种奇葩首先想到的是树套树

考虑以答案为区间二分,每次把权值小于等于\(mid\)的任务扔到左区间,权值大于\(mid\)的任务在树上修改并扔到右区间。

接下来对于一个询问,我们只要判断此时若存在不经过该点的任务,这个询问的答案就大于\(mid\),否则就小于等于\(mid\)

然后就是如何有效维护信息以及修改的问题了。

树上差分

二分之后的每次修改相当于给一条树上路径加/减\(1\)

树上路径?我会树剖!

然而,其实这种题目只要树上差分一下就可以了。

考虑对\((x,y)\)间的路径加上\(1\),只要给\(x,y\)分别加\(1\),然后给\(LCA(x,y)\)\(1\)\(fa_{LCA(x,y)}\)\(1\),则一个点的点权就相当于是它子树内的点权和。

众所周知,子树在\(dfs\)序列上是一段区间。

于是就变成了单点修改、区间查询,显然一个树状数组就好了。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 200000
#define LN 20
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,m,dc,dv[M+5],d,dI[N+5],dO[N+5],dep[N+5],fa[N+5][LN+5];
int ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
struct Op {int op,x,y,v;}q[M+5],sl[M+5],sr[M+5];int ans[M+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
		I void writeNA() {pc('-'),pc('1'),pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
I void dfs(CI x)//dfs初始化
{
	RI i;for(dI[x]=++d,i=1;i<=LN;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
	for(i=lnk[x];i;i=e[i].nxt) fa[x][0]^e[i].to&&
		(dep[e[i].to]=dep[fa[e[i].to][0]=x]+1,dfs(e[i].to),0);dO[x]=d;
}
I int LCA(RI x,RI y)//倍增LCA
{
	RI i;dep[x]<dep[y]&&swap(x,y);
	for(i=0;dep[x]^dep[y];++i) (dep[x]^dep[y])>>i&1&&(x=fa[x][i]);if(x==y) return x;
	for(i=LN;~i;--i) fa[x][i]^fa[y][i]&&(x=fa[x][i],y=fa[y][i]);return fa[x][0];
}
class TreeArray//树状数组
{
	private:
		int a[M+5];
	public:
		I void U(RI x,CI y) {W(x<=n) a[x]+=y,x+=x&-x;}//单点修改
		I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//询问前缀和(差分求出区间和)
}T;int cnt;
I void F5(CI id,CI op)//根据一个询问差分修改
{
	RI z=LCA(q[id].x,q[id].y),v=q[id].v<0?-op:op;cnt+=v;
	T.U(dI[q[id].x],v),T.U(dI[q[id].y],v),T.U(dI[z],-v),z^1&&(T.U(dI[fa[z][0]],-v),0);
}
I void Solve(CI l,CI r,CI L,CI R)//整体二分
{
	RI i;if(l==r) {for(i=L;i<=R;++i) q[i].op&&(ans[q[i].v]=l);return;}//边界
	RI mid=l+r>>1,tl=0,tr=0;for(i=L;i<=R;++i)
	{
		if(!q[i].op) {(abs(q[i].v)<=mid?sl[++tl]:(F5(i,1),sr[++tr]))=q[i];continue;}//对于任务
		((T.Q(dO[q[i].x])-T.Q(dI[q[i].x]-1))^cnt?sr[++tr]:sl[++tl])=q[i];//对于询问
	}
	for(i=L;i<=R;++i) !q[i].op&&abs(q[i].v)>mid&&(F5(i,-1),0);//清空
	for(i=1;i<=tl;++i) q[L+i-1]=sl[i];for(i=1;i<=tr;++i) q[L+tl-1+i]=sr[i];//把分到两边的询问放回原数组
	Solve(l,mid,L,L+tl-1),Solve(mid+1,r,L+tl,R);//递归
}
int main()
{
	RI i,x,y;for(F.read(n,m),i=1;i^n;++i) F.read(x,y),add(x,y),add(y,x);dfs(1);
	for(i=1;i<=m;++i) F.read(q[i].op,q[i].x),!q[i].op&&(F.read(q[i].y,q[i].v),dv[++dc]=q[i].v);
	RI t=0;for(sort(dv+1,dv+dc+1),dc=unique(dv+1,dv+dc+1)-dv-1,i=1;i<=m;++i) switch(q[i].op)
	{
		case 0:q[i].v=lower_bound(dv+1,dv+dc+1,q[i].v)-dv;break;//离散化
		case 1:q[i]=q[q[i].x],q[i].v*=-1;break;case 2:q[i].v=++t;break;//v<0表示删除
	}Solve(0,dc,1,m);
	for(i=1;i<=t;++i) ans[i]?F.writeln(dv[ans[i]]):F.writeNA();return F.clear(),0;//输出答案
}
posted @ 2020-05-28 12:07  TheLostWeak  阅读(124)  评论(0编辑  收藏  举报