【洛谷5311】[Ynoi2011] 成都七中(点分树+树状数组)
大致题意: 有一棵树,每个点有一个颜色。每次询问在保留编号为\([l,r]\)的点时,\(x\)所在连通块的颜色数。
前言
??????
感觉今天状态又回来了?
自从写点分树以来,似乎还是第一次调完样例就能够一遍过掉这种大码量题(好像也不是很大)。
本来打算用这道题打发掉一个上午的。。。
转化:点分树
首先,直接处理连通块显然不太好搞,因此我们要把问题给转化到点分树上去。
考虑对于原树上的一个连通块,我们一定能在点分树上找到一个属于该连通块的点,使得整个连通块都在这个点的子树中。
为什么呢?因为点分树上每个子树是一个连通块,若两点不在同一子树中且没有共同祖先(共同祖先可能可以起到连接作用),就一定不连通。
于是,对于一次询问,我们可以找到\(x\)在点分树上深度最小的一个祖先,满足\(x\)和该祖先连通(即\(x\)到该祖先路径上所有点满足在\([l,r]\)范围内),就可以把询问对象转化为这个祖先。
这样一来,就变成了询问在点分树上某一点的子树内,到根节点路径上所有点都在\([l,r]\)范围内的点的颜色数。
离线乱搞
前面这部分神奇的转化都是看题解的,而之后的做法由于比较套路,我是自己瞎推出来的。
考虑对于子树中的每个点,我们记它到根路径上的最小编号为\(l\),最大编号为\(r\)。
显然,一个点能有贡献,必须要满足询问的左边界小于等于\(l\),右边界大于等于\(r\)。
根据曾做过的一道有点类似的题目的套路,考虑从小到大枚举\(r\),然后用树状数组维护每个左边界的答案。
由于询问的是颜色数,如果贪心地去考虑,很明显,对于每种颜色我们只需维护最大的\(l\)计算答案。
这样就把难以统计的颜色数转化为数值和了。
然后好像也没什么细节了,具体实现可以详见代码。
代码
#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 LN 20
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define pb push_back
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,m,a[N+5],ee,lnk[N+5],ans[N+5];struct edge {int to,nxt;}e[N<<1];
struct data
{
int op,p,l,r;I data(CI f=0,CI i=0,CI a=0,CI b=0):op(f),p(i),l(a),r(b){}
I bool operator < (Con data& o) Con {return r^o.r?r<o.r:op>o.op;}
};vector<data> K[N+5];vector<data>::iterator it;
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 tn (x<<3)+(x<<1)
#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=tn+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
class TreeArray//树状数组
{
private:
int a[N+5];
public:
I void U(RI x,CI v) {W(x) a[x]+=v,x-=x&-x;}//单点修改
I int Q(RI x,RI t=0) {W(x<=n) t+=a[x],x+=x&-x;return t;}//求后缀和
}A;
class DotSolveTree//点分树
{
private:
int Rt,T,Sz[N+5],Mx[N+5],used[N+5],cnt[N+5],lst[N+5];
struct Info {int id,l,r;I Info(CI p=0,CI a=0,CI b=0):id(p),l(a),r(b){}}S[N+5],f[N+5][LN+5];
I void GetRt(CI x,int s,CI lst=0)
{
Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&
e[i].to^lst&&(GetRt(e[i].to,s,x),Sz[x]+=Sz[e[i].to],Gmax(Mx[x],Sz[e[i].to]));
Gmax(Mx[x],s-Sz[x]),Mx[x]<Mx[Rt]&&(Rt=x);
}
I void dfs(CI x,CI lst,RI l,RI r)//遍历子树
{
Gmin(l,x),Gmax(r,x),S[++T]=Info(x,l,r);//开栈存储信息
for(RI i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&e[i].to^lst&&(dfs(e[i].to,x,l,r),0);
}
I void Solve(RI x)
{
used[x]=1,K[x].pb(data(1,x,x,x));
for(RI i=lnk[x],y;i;i=e[i].nxt) if(!used[e[i].to])
{
dfs(e[i].to,x,x,x);W(T) y=S[T].id,
f[y][++cnt[y]]=Info(x,S[T].l,S[T].r),K[x].pb(data(1,y,S[T].l,S[T].r)),--T;//f记录祖先信息,K中op=1表示子树内的点
Rt=0,GetRt(e[i].to,Sz[e[i].to]),Solve(Rt);//继续处理子树
}
}
public:
I void Build() {Mx[Rt=0]=1e9,GetRt(1,n),Solve(Rt);}
I int Find(CI x,CI l,CI r)//找到一个祖先转化询问
{
RI i,t=x;for(i=cnt[x];i;--i) f[x][i].l>=l&&f[x][i].r<=r&&(t=f[x][i].id);return t;
}
I void Calc(CI x)//求解以x为对象的询问
{
for(sort(K[x].begin(),K[x].end()),it=K[x].begin();it!=K[x].end();++it)
it->op?lst[a[it->p]]<it->l&&(A.U(lst[a[it->p]],-1),A.U(lst[a[it->p]]=it->l,1),0)//对于每种颜色维护最大l
:ans[it->p]=A.Q(it->l);//树状数组查询
for(it=K[x].begin();it!=K[x].end();++it) it->op&&(A.U(lst[a[it->p]],-1),lst[a[it->p]]=0);//清空
}
}D;
int main()
{
RI i,x,y,z;for(F.read(n),F.read(m),i=1;i<=n;++i) F.read(a[i]);
for(i=1;i^n;++i) F.read(x),F.read(y),add(x,y),add(y,x);D.Build();//建树
for(i=1;i<=m;++i) F.read(x),F.read(y),F.read(z),z>=x&&z<=y&&(K[D.Find(z,x,y)].pb(data(0,i,x,y)),0);//K中op=0表示询问
for(i=1;i<=n;++i) D.Calc(i);for(i=1;i<=m;++i) F.writeln(ans[i]);return F.clear(),0;//输出答案
}
待到再迷茫时回头望,所有脚印会发出光芒