• ☆ [NOIp2016] 天天爱跑步 「树上差分」


    题目类型:LCA+思维

    传送门:>Here<

    题意:给出一棵树,有(M)个人在这棵树上跑步。每个人都从自己的起点(s[i])跑到终点(t[i]),跑过一条边的时间为1秒。现在每个节点都有一个观察员,节点(i)上的观察员会在第(W[i])秒进行观察,如果有(x)个人此时到达节点(i),则这个观察员能够观察到(x)个人。问所有人跑步结束以后每个观察员可以观察到多少人

    解题思路

    这道题是公认所有(NOIp)中最难的一道题。但其实这道题的数据约定能够给我们很大的提示。

    其实这道题的正解就是对各个部分分的方法的汇总和整合。因此我们先一一分析部分分如何拿

    子任务一 暴力

    由于(N)(1000),可以暴力模拟每个人的行动轨迹。求一个(LCA)暴力走一下就好了,没有思维难度。这个部分分能够首先让你熟悉题目的意思

    子任务二 树退化为链

    乍一看很简单,其实由于(N=99994),也不是那么容易的。事实上,这一个部分分是整道题的精髓。

    由于树已经退化为链,所以所有的路径要么从左往右,要么从右往左。我们可以记录一个桶(b[x])。当我们从左往右扫到(i)时,(b[x])表示以(x)为起点的所有路径中,能够一直延伸到(i)的路径数量。

    记录这个有什么用呢?对于节点(i),由于路径从左往右,所以如果想要一条路径在走到(i)的时候被观察到,那么这条路径的起点(S)一定满足:$$S=i-W[i]$$因此我们在考虑节点(i)时,只需要考虑以(i-W[i])为起点的能够延伸到(i)的路径条数,这些路径一定能够在(i)点被观察到。因此我们需要统计的就是(b[i-W[i])。那么从右往左的路径也类似,我们只需要统计(b[i+W[i]])就可以了

    那么如何维护(b)数组呢?我们可以首先记录每个点的起点个数,然后过了终点以后减去终点对应的那些起点。例如(2)开头只有(3)条路径:((2,4) (2,3) (2,7))。我们扫描到(2)的时候让(b[2]+=3),代表从(2)开始能够延伸到(2)的路径有(3)条。然而扫描到(4)的时候事实上只有(2)条路径满足了,因此在统计完(3)以后应当(--b[2]),因为((2,3))这条路径不可能延伸到(4)了。但是如果还存在一条路径((1,3))呢?一个终点可能对应多个起点,所以我们需要用一个(vector)来维护每个终点对应的所有起点,在扫描到这个终点的时候扫一遍(vector)全部减掉

    事实上,这就是差分。只不过普通差分只需要统计次数,而不会规定起点的深度。对于这个规定起点深度的问题,自然需要排除深度不正确的起点。所以才会需要用(vector)来记录

    子任务三 (S=1)

    我们发现,当(S=1)时,所有路径都是从根节点往下。这样的路径有什么特点?对于任意一个点(u),由于它的路径一定是从根节点出发的,因此根节点到它的路径长度就是它的深度(根节点深度为0)。也就是说,这要满足(dep[u]=W[u]),这个点就是能够被观察到的

    那么到底会被观察到几次呢?这就取决于根节点到它有几条路径。或者用更便于统计的方法:(u)及它的子树内有多少个终点

    子任务四 (T=1)

    和前者反了一下,但也更巧妙。所有路径都是从下往上到根节点的

    我们发现一条路径上的一个点如果要被观察到,首先应该满足$$dep[S]-dep[u]=W[u]$$移项我们发现,也就是$$dep[u]+W[u]=dep[S]$$也就是说我们需要找的答案等价于,在节点(u)及其子树中,有多少起点的深度为(dep[u]+W[u])

    联系链状那一部分的做法,其实就是把一维的拓展为了树。由于所有路径都是向上的,也就等价于链状中所有路径都是从右向左的。我们记录一个桶(b[x])表示深度为(x)的起点有多少个。因此我们还是一样,每遇到一个起点就++。特别的幸运的是,我们还不需要开一个(vector),遇到终点就减去对应起点,因为终点一定是根节点。每一次统计的答案也就是(b[dep[u]+W[u]])

    但是有一个问题,我们这样的做法的正确性必须保证是在(u)的子树内。然而深度为(x)的起点有可能是之前在别的子树内统计的。怎么办?其实我们是从下往上走,也就是要在处理完(u)的全部子树以后才来处理(u),于是我们其实只需要考虑新增加的部分就可以了。也就是在(DFS)下去之前先记录一个(b[dep[u]+W[u]])存为(tmp),在(DFS)结束以后的答案也就是(b[dep[u]+W[u]]-tmp)

    附上如上子任务的代码 Code

    正解

    对于一般性的数据,所有路径一定是从下往上,经过(LCA),再从上往下。而数据提示了我们(S=1)(T=1)。这就是在暗示我们将路径分开来考虑!

    因此,就像链的情况,正解就是需要判断起点深度的树上差分!

    首先考虑所有的((S,LCA))的路径。这些路径的特性与(T=1)的路径是一样的。都需要满足(dep[u]+W[u]=dep[S]),唯一的不同就在于需要像链状一样,维护一个(vector)记录终点对应的起点,在过终点时减去即可。注意,这里的终点不是指(T),而是(LCA)

    最最难理解的就是((LCA,T))这一部分了。我们依旧分析路劲的特征。发现被观察到的点一定满足$$W[u]+dep[T]-dep[u]=dis(S,T)$$移项得到$$dep[u]-W[u]=dep[T]-dis(S,T)$$。但是不像之前的(dep[S]),这个(dep[T]-dis(S,T))是在树上是没有实际意义的(一定要说有的话,那就是将向上的半截路径翻到上面去的深度)。

    于是没有办法,我们需要类比((S,LCA))部分的做法来处理这个部分。我们依然是从下往上做,与之前相反,每遇到一个(T)就代表着进入了一条新的路径,每遇到一个(LCA)就意味着离开了一条路径。并且我们每遇到一个终点,标记的不是对应起点,而是对应的(dep[T]-dis(S,T));每遇到一个(LCA),减去的也是该路径对应的(dep[T]-dis(S,T))。因此我们此时需要两个(vector)来存了

    综上就是算法的基本思想。这里还有两个细节需要分析:

    • 注意到(dep[T]-dis(S,T))很有可能是个负数,这让(c++)开通特别麻烦。一个比较粗暴的方式就是全部一律右移(MAXN)

    • 我们将一条路径剖为了((S,LCA) 和 (LCA,T))。如果(LCA)恰好是能够被观察到的,不就重复统计了一遍?所以我们需要在最后结算的时候进行修改,如果当前路径的(LCA)会被看到,也就是满足$$dep[S]-dep[LCA]=W[LCA]$$那么就应当减去一个(1)

    Code

    倍增的数组开太小了调试了近一个小时(qwq)

    /*By DennyQi 2018*/
    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    #define  r  read()
    using namespace std;
    typedef long long ll;
    const int MAXN = 300010;
    const int MAXM = 600010;
    const int INF = 1061109567;
    inline int Max(const int a, const int b){ return (a > b) ? a : b; }
    inline int Min(const int a, const int b){ return (a < b) ? a : b; }
    inline int read(){
        int x = 0; int w = 1; register char c = getchar();
        for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
        if(c == '-') w = -1, c = getchar();
        for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
    }
    int N,M,x,y,len;
    int first[MAXM],nxt[MAXM],to[MAXM],cnt;
    int f[MAXN][25],dep[MAXN],W[MAXN],s[MAXN],t[MAXN],lca[MAXN],nums[MAXN],b[MAXN],bk[MAXN*2],ans[MAXN];
    vector <int> S[MAXN];
    vector <int> T1[MAXN], T2[MAXN];
    inline void add(int u, int v){
    	to[++cnt]=v, nxt[cnt]=first[u], first[u]=cnt;
    }
    void BZ_INIT(int u, int _f, int d){
    	int v;
    	dep[u] = d; f[u][0] = _f;
    	for(int i = 1; (1 << i) <= d; ++i){
    		f[u][i] = f[f[u][i-1]][i-1];
    	}
    	for(int i = first[u]; i; i = nxt[i]){
    		if((v = to[i]) == _f) continue;
    		BZ_INIT(v, u, d+1);
    	}
    }
    inline int LCA(int a, int b){
    	if(dep[a] < dep[b]) swap(a, b);
    	for(int i = 20; i >= 0; --i){
    		if(dep[a] - (1 << i) >= dep[b]) a = f[a][i];
    	}
    	if(a == b) return a;
    	for(int i = 20; i >= 0; --i){
    		if(f[a][i] == f[b][i]) continue;
    		a = f[a][i], b = f[b][i];
    	}
    	return f[a][0];
    }
    void DFS1(int u, int _f){
    	int v, tmp = b[dep[u] + W[u]];
    	for(int i = first[u]; i; i = nxt[i]){
    		if((v = to[i]) == _f) continue;
    		DFS1(v, u);
    	}
    	b[dep[u]] += nums[u];
    	ans[u] += b[dep[u] + W[u]] - tmp;
    	for(int i = 0, sz = S[u].size(); i < sz; ++i){
    		--b[S[u][i]];
    	}
    }
    void DFS2(int u, int _f){
    	int v, tmp = bk[dep[u] - W[u] + MAXN];
    	for(int i = first[u]; i; i = nxt[i]){
    		if((v = to[i]) == _f) continue;
    		DFS2(v, u);
    	}
    	for(int i = 0, sz = T1[u].size(); i < sz; ++i){
    		++bk[T1[u][i] + MAXN];
    	}
    	ans[u] += bk[dep[u] - W[u] + MAXN] - tmp;
    	for(int i = 0, sz = T2[u].size(); i < sz; ++i){
    		--bk[T2[u][i] + MAXN];
    	}
    }
    int main(){
    	N = r, M = r;
    	for(int i = 1; i < N; ++i){
    		x = r, y = r;
    		add(x, y);
    		add(y, x);
    	}
    	BZ_INIT(1, 0, 0);
    	for(int i = 1; i <= N; ++i){
    		W[i] = r;
    	}
    	for(int i = 1; i <= M; ++i){
    		s[i] = r, t[i] = r;
    		lca[i] = LCA(s[i], t[i]);
    		len = dep[s[i]] + dep[t[i]] - 2 * dep[lca[i]];
    		++nums[s[i]];
    		S[lca[i]].push_back(dep[s[i]]);
    		T1[t[i]].push_back(dep[t[i]] - len);
    		T2[lca[i]].push_back(dep[t[i]] - len);
    	}
    	DFS1(1, 0);
    	DFS2(1, 0);
    	for(int i = 1; i <= M; ++i){
    		if(dep[s[i]] - dep[lca[i]] == W[lca[i]]){
    			--ans[lca[i]];
    		}
    	}
    	for(int i = 1; i <= N; ++i){
    		printf("%d ", ans[i]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    搞笑的菜名
    sad news
    转载:比尔盖茨给adobe CEO的五点意见(歪版)/ 很搞笑 ;)
    [ZZ]TDSCDMA试商用网已经建成 开始向用户放号
    关于Java IDE
    候捷要来南京了吗?
    读书笔记:《Java2 教程》(二)
    [ZZ]当今世界十大定律
    久违了,博客园
    解决小型局域网中xp系统的机器不能共享资源的问题
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9522068.html
Copyright © 2020-2023  润新知