【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;//输出答案
}
待到再迷茫时回头望,所有脚印会发出光芒