5.树上问题

树上问题

开题顺序: \(ACHKG\)

\(A\) CF600E Lomsat gelral

\(B\) CF708C Centroids

\(C\) CF1706E Qpwoeirut and Vertices

\(D\) luogu P2491 [SDOI2011] 消防

  • 多倍经验: luogu P1099 [NOIP2007 提高组] 树网的核

  • 选择直径上的边一定不劣。

  • 因为边权没有负数,所以用两遍 \(DFS\) 求出任意一条直径。设其上面的节点分别为 \(u_{1 \sim t}\)

  • \(f_{i}\) 表示从 \(i\) 出发,不经过所选出的直径上的点能到达的距离最远的点的距离。

  • 此时 \(u_{i},u_{j}(1 \le i \le j \le t)\) 对答案的贡献为 \(\max\limits(\max\limits_{k=i}^{j}\{ f_{u_{k}} \},dis_{u_{1},u_{i}},dis_{u_{t},u_{j}})\) 。最终的式子显然是 \(\min\limits_{1 \le i \le j \le t} \{ \max\limits(\max\limits_{k=i}^{j}\{ f_{u_{k}} \},dis_{u_{1},u_{i}},dis_{u_{t},u_{j}}) \}\)

  • 而在 \(i\) 固定时, \(j\) 越靠近 \(t\) 越优,双指针维护即可。

  • \(\max\limits_{k=i}^{j}\{ f_{u_{k}} \}\) 单调队列 \(O(n)\) 或者 \(ST\)\(O(n \log n)\) 维护暂且不提,考虑关于 \(\max\limits_{k=i}^{j}\{ f_{u_{k}} \}\) 的一些其他性质。

  • 因为都是直径上的端点,容易有 \(f_{u_{k}} \le dis_{u_{1},u_{k}}\)\(f_{u_{k}} \le dis_{u_{t},u_{k}}\)

  • \(\begin{cases} \forall k \in [1,i),f_{u_{k}} \le dis_{u_{1},u_{k}} \le dis_{u_{1},u_{i}} \\ \forall k \in (j,n],f_{u_{k}} \le dis_{u_{t},u_{k}} \le dis_{u_{t},u_{j}} \end{cases}\) ,故最终结果可以改写为 \(\min\limits_{1 \le i \le j \le t} \{ \max\limits(\max\limits_{k=1}^{t}\{ f_{u_{k}} \},dis_{u_{1},u_{i}},dis_{u_{t},u_{j}}) \}\)

    点击查看代码
    struct node
    {
    	int nxt,to,w;
    }e[600010];
    int head[600010],dis[600010],fa[600010],c[600010],vis[600010],sum[600010],f[600010],d[600010],cnt=0;
    void add(int u,int v,int w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void dfs1(int x,int father,int w)
    {
    	c[x]=w;
    	fa[x]=father;
    	dis[x]=dis[father]+w;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			dfs1(e[i].to,x,e[i].w);
    		}
    	}
    }
    void dfs2(int x)
    {
    	vis[x]=1;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(vis[e[i].to]==0)
    		{
    			dfs2(e[i].to);
    			f[x]=max(f[x],f[e[i].to]+e[i].w);
    		}
    	}
    }
    int main()
    {
    	int n,s,u,v,w,rt1=0,rt2=0,ans=0x7f7f7f7f,maxx=0,i,j;
    	cin>>n>>s;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v>>w;
    		add(u,v,w);
    		add(v,u,w);
    	}
    	dfs1(1,0,0);
    	for(i=1;i<=n;i++)
    	{
    		rt1=(dis[i]>dis[rt1])?i:rt1;
    	}
    	dfs1(rt1,0,0);
    	for(i=1;i<=n;i++)
    	{
    		rt2=(dis[i]>dis[rt2])?i:rt2;
    	}
    	while(rt2!=0)
    	{
    		vis[rt2]=1;
    		d[0]++;
    		d[d[0]]=rt2;
    		rt2=fa[rt2];
    	}
    	reverse(d+1,d+1+d[0]);
    	for(i=1;i<=d[0];i++)
    	{
    		dfs2(d[i]);
    		maxx=max(maxx,f[d[i]]);
    		sum[i]=sum[i-1]+c[d[i]];
    	}
    	for(i=j=1;i<=d[0];i++)
    	{
    		while(j+1<=d[0]&&sum[j+1]-sum[i]<=s)
    		{
    			j++;
    		}
    		ans=min(ans,max(maxx,max(sum[i],sum[d[0]]-sum[j])));
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(E\) luogu P4253 [SCOI2015] 小凸玩密室

\(F\) luogu P8990 [北大集训 2021] 小明的树

\(G\) BZOJ3786 星系探索

  • 貌似是伪 \(ETT\) 板子。

  • 考虑维护整棵树的欧拉序列,设点 \(x\) 的进栈序为 \(in_{x}\) ,出栈序为 \(out_{x}\) ,进栈时的贡献为 \(w_{x}\) ,出栈时的贡献为 \(-w_{x}\)

  • 操作 \(1\) 的链求和就转化成了 \([1,l_{x}]\) 的区间求和,且容易扩展至树上任意两点间求和。

  • 操作 \(2\) 的子树求和直接对着区间打懒惰标记。

  • 操作 \(3\) 的换父亲可以转化为 \([in_{x},out_{x}]\) 接到新父亲的入栈序 \(in_{fa}\) 后面,平衡树维护序列即可。

  • 在使用 \(FHQ-Treap\) 时因为存在换父亲操作,原序列中的顺序可能发生了改变,需要先查出其 \(rank\) 再进行分裂。具体地,从 \(x\) 往上跳父亲,沿途加上左子树的大小加 \(1\)

    点击查看代码
    struct node
    {
    	ll nxt,to;
    }e[200010];
    ll head[200010],w[200010],in[200010],out[200010],cnt=0,tot=0;
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    struct BST
    {	
    	ll rt_sum,root;
    	struct FHQ_Treap
    	{
    		ll son[2],fa,val,rnd,siz,dir,len,sum,lazy;
    	}tree[200010];
    	#define lson(rt) (tree[rt].son[0])
    	#define rson(rt) (tree[rt].son[1])
    	#define fa(rt) (tree[rt].fa)
    	BST()
    	{
    		rt_sum=root=0;
    		srand(time(0));
    	}
    	void pushup(ll rt)
    	{
    		tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+1;
    		tree[rt].len=tree[lson(rt)].len+tree[rson(rt)].len+tree[rt].dir;
    		tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum+tree[rt].val;
    		if(lson(rt)!=0)
    		{
    			fa(lson(rt))=rt;
    		}
    		if(rson(rt)!=0)
    		{
    			fa(rson(rt))=rt;
    		}
    	}
    	ll build_rt(ll val,ll dir)
    	{
    		rt_sum++;
    		lson(rt_sum)=rson(rt_sum)=tree[rt_sum].lazy=0;
    		tree[rt_sum].val=tree[rt_sum].sum=val;
    		tree[rt_sum].len=tree[rt_sum].dir=dir;
    		tree[rt_sum].rnd=rand();
    		tree[rt_sum].siz=1;
    		return rt_sum;
    	}
    	void pushlazy(ll rt,ll lazy)
    	{
    		tree[rt].lazy+=lazy;
    		tree[rt].val+=tree[rt].dir*lazy;
    		tree[rt].sum+=tree[rt].len*lazy;
    	}
    	void pushdown(ll rt)
    	{
    		if(rt!=0&&tree[rt].lazy!=0)
    		{
    			pushlazy(lson(rt),tree[rt].lazy);
    			pushlazy(rson(rt),tree[rt].lazy);
    			tree[rt].lazy=0;
    		}
    	}
    	void split(ll rt,ll k,ll &ls,ll &rs)
    	{
    		if(rt==0)
    		{
    			ls=rs=0;
    			return;
    		}
    		pushdown(rt);
    		if(tree[lson(rt)].siz+1<=k)
    		{
    			ls=rt;
    			split(rson(ls),k-tree[lson(rt)].siz-1,rson(ls),rs);
    		}
    		else
    		{
    			rs=rt;
    			split(lson(rs),k,ls,lson(rs));
    		}
    		pushup(rt);
    	}
    	ll merge(ll rt1,ll rt2)
    	{
    		if(rt1==0||rt2==0)
    		{
    			return rt1+rt2;
    		}
    		pushdown(rt1);
    		pushdown(rt2);
    		if(tree[rt1].rnd<tree[rt2].rnd)
    		{
    			rson(rt1)=merge(rson(rt1),rt2);
    			pushup(rt1);
    			return rt1;	
    		}
    		else
    		{
    			lson(rt2)=merge(rt1,lson(rt2));
    			pushup(rt2);
    			return rt2;
    		}
    	}
    	void insert(ll val,ll dir)
    	{
    		root=merge(root,build_rt(val,dir));
    	}
    	ll query_rk(ll rt)
    	{
    		ll ans=tree[lson(rt)].siz+1;
    		for(;fa(rt)!=0;rt=fa(rt))
    		{
    			ans+=(rson(fa(rt))==rt)*(tree[lson(fa(rt))].siz+1);
    		}
    		return ans;
    	}
    	void change_fa(ll x,ll y)
    	{
    		ll ls,rs,mid;
    		split(root,query_rk(out[x]),ls,rs);
    		fa(ls)=fa(rs)=0;//记得更改父亲
    		split(ls,query_rk(in[x])-1,ls,mid);
    		fa(ls)=fa(mid)=0;
    		root=merge(ls,rs);
    		fa(root)=0;
    		split(root,query_rk(in[y]),ls,rs);
    		root=merge(merge(ls,mid),rs);
    		fa(root)=0;
    	}
    	void update(ll x,ll val)
    	{
    		ll ls,rs,mid;
    		split(root,query_rk(out[x]),ls,rs);
    		fa(ls)=fa(rs)=0;
    		split(ls,query_rk(in[x])-1,ls,mid);
    		fa(ls)=fa(mid)=0;
    		pushlazy(mid,val);
    		root=merge(merge(ls,mid),rs);
    		fa(root)=0;
    	}
    	ll query(ll x)
    	{
    		ll ls,rs,ans;
    		split(root,query_rk(in[x]),ls,rs);
    		ans=tree[ls].sum;
    		root=merge(ls,rs);
    		return ans;
    	}
    }T;
    void dfs(ll x)
    {
    	tot++;
    	in[x]=tot;
    	T.insert(w[x],1);
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		dfs(e[i].to);
    	}
    	tot++;
    	out[x]=tot;
    	T.insert(-w[x],-1);
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	ll n,m,u,v,i;
    	char pd;
    	cin>>n;
    	for(i=2;i<=n;i++)
    	{
    		cin>>u;
    		add(u,i);
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>w[i];
    	}
    	dfs(1);
    	cin>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>pd>>u;
    		if(pd=='Q')
    		{
    			cout<<T.query(u)<<endl;
    		}
    		if(pd=='C')
    		{
    			cin>>v;
    			T.change_fa(u,v);
    		}
    		if(pd=='F')
    		{
    			cin>>v;
    			T.update(u,v);
    		}
    	}
    	return 0;
    }
    

\(H\) CF825G Tree Queries

  • \(dis_{u,v}\) 表示从 \((u,v)\) 间的最小边权,询问等价于求 \(\min\limits_{i=1}^{|black|} \{ dis_{x,black_{i}} \}\)

  • 不妨钦定第一个黑色节点为根节点,上式等价于 \(\min(dis_{x,black_{1}},\min\limits_{i=2}^{|black|}\{ dis_{black_{i},black_{1}} \})\)

  • 发现只有黑点会发现影响,直接修改即可。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[2000010];
    int head[2000010],dis[2000010],cnt=0;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(int x,int fa)
    {
    	dis[x]=(fa==0)?x:min(dis[fa],x);
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x);
    		}
    	}
    }
    int main()
    {
    	int n,m,u,v,pd,x,rt=0,ans=0,minn=0x7f7f7f7f,i;
    	scanf("%d%d",&n,&m);
    	for(i=1;i<=n-1;i++)
    	{
    		scanf("%d%d",&u,&v);
    		add(u,v);
    		add(v,u);
    	}	
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d",&pd,&x);
    		x=(ans+x)%n+1;
    		if(pd==1)
    		{
    			if(rt==0)
    			{
    				rt=x;
    				dfs(rt,0);
    			}
    			minn=min(minn,dis[x]);
    		}
    		else
    		{
    			ans=min(minn,dis[x]);
    			printf("%d\n",ans);
    		}
    	}
    	return 0;
    }
    

\(I\) CF1381D The Majestic Brown Tree Snake

\(J\) luogu P2664 树上游戏

\(K\) luogu P5305 [GXOI/GZOI2019] 旧词

  • 将询问离线下来,进行扫描线。

  • \(k=1\) 的话就是 luogu P4211 [LNOI2014] LCA 了。

  • 仍考虑原题中从根节点到 \(x\) 的路径上的点打标记进行树上差分的做法。

  • 具体地,设 \(x\) 的权值为 \(c_{x}=dep_{x}^{k}-dep_{fa_{x}}^{k}\) ,那么 \(1 \to x\) 路径上的点权和就是 \(dep_{x}^{k}\)

  • 在移动右指针 \(r\) 的过程中,需要将 \(1 \to r\) 路径上点 \(x\) 的权值都加上对应的 \(c_{x}\) 。线段树维护每个区间被加了几遍即可(也可以当历史版本和线段树维护最后一个版本和来做)。

    点击查看代码
    const ll p=998244353;
    struct node
    {
    	ll nxt,to;
    }e[500010];
    ll head[500010],fa[500010],siz[500010],son[500010],dep[500010],mi[500010],c[500010],cc[500010],top[500010],dfn[500010],ans[500010],cnt=0,tot=0;
    vector<pair<ll,ll> >q[500010];
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    void dfs1(ll x,ll k)
    {
    	siz[x]=1;
    	dep[x]=dep[fa[x]]+1;
    	mi[x]=qpow(dep[x],k,p);
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		dfs1(e[i].to,k);
    		siz[x]+=siz[e[i].to];
    		son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
    	}
    }
    void dfs2(ll x,ll id)
    {
    	top[x]=id;
    	tot++;
    	dfn[x]=tot;
    	cc[tot]=c[x];
    	if(son[x]!=0)
    	{
    		dfs2(son[x],id);
    		for(ll i=head[x];i!=0;i=e[i].nxt)
    		{
    			if(e[i].to!=son[x])
    			{
    				dfs2(e[i].to,e[i].to);
    			}
    		}
    	}
    }
    struct SMT
    {
    	struct SegementTree
    	{
    		ll sum,lazy,val;
    	}tree[2000010];
    	ll lson(ll x)
    	{
    		return x*2;
    	}
    	ll rson(ll x)
    	{
    		return x*2+1;
    	}
    	void pushup(ll rt)
    	{
    		tree[rt].sum=(tree[lson(rt)].sum+tree[rson(rt)].sum)%p;
    	}
    	void build(ll rt,ll l,ll r)
    	{
    		if(l==r)
    		{
    			tree[rt].val=cc[l];
    			return;
    		}
    		ll mid=(l+r)/2;
    		build(lson(rt),l,mid);
    		build(rson(rt),mid+1,r);
    		tree[rt].val=(tree[lson(rt)].val+tree[rson(rt)].val)%p;
    	}
    	void pushdown(ll rt)
    	{
    		if(tree[rt].lazy!=0)
    		{
    			tree[lson(rt)].sum=(tree[lson(rt)].sum+tree[rt].lazy*tree[lson(rt)].val%p)%p;
    			tree[rson(rt)].sum=(tree[rson(rt)].sum+tree[rt].lazy*tree[rson(rt)].val%p)%p;
    			tree[lson(rt)].lazy+=tree[rt].lazy;
    			tree[rson(rt)].lazy+=tree[rt].lazy;
    			tree[rt].lazy=0;
    		}
    	}
    	void update(ll rt,ll l,ll r,ll x,ll y,ll val)
    	{
    		if(x<=l&&r<=y)
    		{
    			tree[rt].sum=(tree[rt].sum+val*tree[rt].val%p)%p;
    			tree[rt].lazy+=val;
    			return;
    		}
    		pushdown(rt);
    		ll mid=(l+r)/2;
    		if(x<=mid)
    		{
    			update(lson(rt),l,mid,x,y,val);
    		}
    		if(y>mid)
    		{
    			update(rson(rt),mid+1,r,x,y,val);
    		}
    		pushup(rt);
    	}
    	ll query(ll rt,ll l,ll r,ll x,ll y)
    	{
    		if(x<=l&&r<=y)
    		{
    			return tree[rt].sum;
    		}
    		pushdown(rt);
    		ll mid=(l+r)/2,ans=0;
    		if(x<=mid)
    		{
    			ans=(ans+query(lson(rt),l,mid,x,y))%p;
    		}
    		if(y>mid)
    		{
    			ans=(ans+query(rson(rt),mid+1,r,x,y))%p;
    		}
    		return ans;
    	}
    }T;
    void update(ll u,ll v,ll val,ll n)
    {
    	while(top[u]!=top[v])
    	{
    		if(dep[top[u]]>dep[top[v]])
    		{
    			T.update(1,1,n,dfn[top[u]],dfn[u],val);
    			u=fa[top[u]];
    		}
    		else
    		{
    			T.update(1,1,n,dfn[top[v]],dfn[v],val);
    			v=fa[top[v]];
    		}
    	}
    	if(dep[u]>dep[v])
    	{
    		T.update(1,1,n,dfn[v],dfn[u],val);
    	}
    	else
    	{
    		T.update(1,1,n,dfn[u],dfn[v],val);
    	}
    }
    ll query(ll u,ll v,ll n)
    {
    	ll ans=0;
    	while(top[u]!=top[v])
    	{
    		if(dep[top[u]]>dep[top[v]])
    		{
    			ans=(ans+T.query(1,1,n,dfn[top[u]],dfn[u]))%p;
    			u=fa[top[u]];
    		}
    		else
    		{
    			ans=(ans+T.query(1,1,n,dfn[top[v]],dfn[v]))%p;
    			v=fa[top[v]];
    		}
    	}
    	if(dep[u]>dep[v])
    	{
    		ans=(ans+T.query(1,1,n,dfn[v],dfn[u]))%p;
    	}
    	else
    	{
    		ans=(ans+T.query(1,1,n,dfn[u],dfn[v]))%p;
    	}
    	return ans;
    }
    int main()
    {
    	ll n,m,k,x,y,i,j;
    	cin>>n>>m>>k;
    	for(i=2;i<=n;i++)
    	{
    		cin>>fa[i];
    		add(fa[i],i);
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>x>>y;
    		q[x].push_back(make_pair(y,i));
    	}
    	dfs1(1,k);
    	for(i=1;i<=n;i++)
    	{
    		c[i]=(mi[i]-mi[fa[i]]+p)%p;
    	}
    	dfs2(1,1);
    	T.build(1,1,n);
    	for(i=1;i<=n;i++)
    	{
    		update(i,1,1,n);
    		for(j=0;j<q[i].size();j++)
    		{
    			ans[q[i][j].second]=query(q[i][j].first,1,n);
    		}
    	}
    	for(i=1;i<=m;i++)
    	{
    		cout<<ans[i]<<endl;
    	}
    	return 0;
    }
    

\(L\) luogu P5306 [COCI2018-2019#5] Transport

posted @ 2024-11-04 17:02  hzoi_Shadow  阅读(45)  评论(2编辑  收藏  举报
扩大
缩小