• [NOIP2016] 天天爱跑步


    前言

    看这道题不爽很久了,但一直没有开它,原因是我不会(我太菜了),看了题解还是写不来,因为我不会线段树合并。

    然后今天学了dsu on tree这种神奇的科技,成功把它A了,效率吊打线段树合并。

    于是写篇题解纪念一下。

    题目链接

    洛谷P1600 天天爱跑步

    算法概述

    不带修改的树上路径信息的维护,很容易想到树上差分。

    我们考虑一条路径,会对哪些点产生贡献,也就是题目描述中的一个人会被哪些观察员观察到。

    假设这条路径为 ((s,t)),点为 (x),我们分两部分考虑:

    • 若点 (x)(s)(lca(s,t))(含)的路径上,则若满足 (w[x]=dep[s]-dep[x]) 则会在 (x) 上产生 (1) 的贡献。

      将式子变形:(w[x]+dep[x]=dep[s]),我们就可以发现一个事情,对于任何一个点 (p),这种情况的点数即为所有路径中起点深度为 (w[p]+dep[p]) 的数量。

      我们可以形象化地理解为,有 (m) 个人,每个人会给 (s)(lca(s,t)) 路径上的每个点发放一个类型为 (dep[s]) 的物品,而我们要求的就是对于每个点 (x),其上类型为 (w[x]+dep[x]) 的物品有多少个。

      再抽象一点,即一条路径 (s→lca(s,t)),会将路径上每个点处权值为 (dep[s]) 的位置覆盖一次,最终求每个点 (x) 上权值为 (w[x]+dep[x]) 的位置被覆盖了几次。

      此时,树上差分已经呼之欲出了。我们在每个点上开一个桶,对于每条路径 ((s,t)),在点 (s) 让下标为 (dep[s]) 的值(+1),在点 (fa[lca(s,t)]) 上让下标为 (dep[s]) 的值 (-1),其中 (fa[i]) 表示 (i) 的父亲节点,最后每个点的信息应该是以其为根的子树和,这部分答案即为该点的桶内下标为 (w[x]+dep[x]) 的值。

    • 若点 (x)(lca(s,t))(不含)的路径上,设路径长度为 (dis),则若满足 (dis-(dep[t]-dep[x])=w[x]),根据 (dis=dep[s]+dep[t]-2*dep[lca(s,t)]),可将式子变形为:(w[x]-dep[x]=dep[s]-2*dep[lca(s,t)])

      那么同样应用树上差分,在点 (t) 上使下标为 (dep[s]-2*dep[lca(s,t)]) 的值 (+1),在 (lca(s,t)) 上使下标为 (dep[s]-2*dep[lca(s,t)]) 的值 (-1),最后每个点的信息统计子树和,答案即为点上下标为 (w[x]-dep[x]) 的值。

    我们设第一种情况开的桶为 (up),第二种情况开的桶为 (down),那么对于每个点 (x),其最终答案即为 (up[w[x]+dep[x]]+down[w[x]-dep[x]])

    但问题是,我们在每个点上开两个桶是显然不可能的,而且就算空间能够支持每个点开两个桶,但我们在统计过程中,合并两个桶的时间复杂度也直接爆炸。

    所以我们只能在全局开两个桶。

    那么怎么维护子树信息呢?

    不带修改的子树信息维护,正是dsu on tree大显身手的领域。

    所以在每个点上开个 (vector),把每个点要维护的信息放进去,然后dsu on tree,需要统计信息的时候再把信息拿出来统计就好了。

    每个点维护信息的时间均摊是 (frac{4m}{n}) 的,每个点最多会被统计 (O(logn)) 次,一共有 (n) 个点,所以最后的时间复杂度是 (O(mlogn)) 的。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    using namespace std;
    const int N=3e5+10;
    struct Edge{
    	int to,nex;
    }edge[N<<1];int idx;
    int h[N];
    
    void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}
    
    int fa[N],dep[N],siz[N];
    int son[N],top[N],w[N];
    vector<int> info[N][4];
    int up[N<<1],BUF[N<<2],*down=&BUF[N<<1];
    int ans[N];
    int n,m;
    
    void dfs1(int p,int f)
    {
    	fa[p]=f;
    	dep[p]=dep[f]+1;
    	siz[p]=1;
    	int max_son=0;
    	for(int i=h[p];~i;i=edge[i].nex)
    	{
    		int to=edge[i].to;
    		if(to==f)continue;
    		dfs1(to,p);
    		if(siz[to]>max_son)max_son=siz[to],son[p]=to;
    		siz[p]+=siz[to]; 
    	}
    }
    
    void dfs2(int p,int t)
    {
    	top[p]=t;
    	if(!son[p])return;
    	dfs2(son[p],t);
    	for(int i=h[p];~i;i=edge[i].nex)
    	{
    		int to=edge[i].to;
    		if(to==fa[p]||to==son[p])continue;
    		dfs2(to,to);
    	}
    }
    
    inline int lca(int x,int y)
    {
    	while(top[x]!=top[y])
    	{
    		if(dep[top[x]]<dep[top[y]])swap(x,y);
    		x=fa[top[x]];
    	}
    	return dep[x]<dep[y]?x:y; 
    }
    
    void calc(int p,int flag,int val)
    {
    	for(int i=0;i<info[p][0].size();i++)up[info[p][0][i]]+=val;
    	for(int i=0;i<info[p][1].size();i++)up[info[p][1][i]]-=val;
    	for(int i=0;i<info[p][2].size();i++)down[info[p][2][i]]+=val;
    	for(int i=0;i<info[p][3].size();i++)down[info[p][3][i]]-=val;
    	for(int i=h[p];~i;i=edge[i].nex)
    	{
    		int to=edge[i].to;
    		if(to==fa[p]||to==flag)continue;
    		calc(to,flag,val);
    	}
    }
    
    void dfs(int p,int keep)
    {
    	for(int i=h[p];~i;i=edge[i].nex)
    	{
    		int to=edge[i].to;
    		if(to==fa[p]||to==son[p])continue;
    		dfs(to,0);
    	}
    	if(son[p])dfs(son[p],1);
    	calc(p,son[p],1);
    	ans[p]=up[dep[p]+w[p]]+down[w[p]-dep[p]];
    	if(!keep)calc(p,0,-1);
    }
    
    int main()
    {
    	memset(h,-1,sizeof h);
    	scanf("%d%d",&n,&m);
    	for(int i=1,a,b;i<=n-1;i++)
    	{
    		scanf("%d%d",&a,&b);
    		add_edge(a,b);
    		add_edge(b,a);
    	}
    	for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    	
    	dfs1(1,0);
    	dfs2(1,1);
    	
    	for(int i=1,s,t;i<=m;i++)
    	{
    		scanf("%d%d",&s,&t);
    		int z=lca(s,t);
    		info[s][0].push_back(dep[s]);
    		info[fa[z]][1].push_back(dep[s]);
    		info[t][2].push_back(dep[s]-2*dep[z]);
    		info[z][3].push_back(dep[s]-2*dep[z]);
    	}
    	
    	dfs(1,1);
    	
    	for(int i=1;i<=n;i++)printf("%d ",ans[i]);
    	
    	return 0;
    }
    
  • 相关阅读:
    高精度 模板
    《图论》邻接表
    模板整理
    H
    C
    同余定理的应用(处理大数
    H
    1153: 简易版最长序列
    1152: 二分搜索
    1151: 大整数加法(正数)
  • 原文地址:https://www.cnblogs.com/ninedream/p/13918202.html
Copyright © 2020-2023  润新知