zoukankan      html  css  js  c++  java
  • 【洛谷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('
    ');}
    		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;//输出答案
    }
    
  • 相关阅读:
    centos7 挂载未分配的硬盘空间 (测试可用)
    虚拟化下Centos7 扩容根分区
    Centos7 虚拟机挂载未分配的空间
    Linux下对LVM逻辑卷分区大小调整 [针对xfs和ext4文件系统]
    XFS vs EXT4
    虚拟化下Centos7 扩容根分区
    centos7下扩展根分区(图文详解)
    解决删除镜像时image is referenced in multiple repositories
    linux 根目录扩容方法
    Linux系统扩容根目录磁盘空间的操作方法
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5311.html
Copyright © 2011-2022 走看看