zoukankan      html  css  js  c++  java
  • 题解 P1600 【天天爱跑步】

    题目链接

    Solution 天天爱跑步

    题目大意:给定一棵树,有(m)个人会从(u)沿着最短路径跑到(v),通过每条边的时间为(1),每个人于时刻(0)同时起跑.如果一个人在时刻(w_i)跑到点(i)那么点(i)答案(+1),求每个点答案

    树上差分


    分析:

    我们直接统计观察员的答案不方便的话我们可以考虑统计每段路径对答案的贡献

    然后此题关键是拆分路径

    假如(lca(u,v) = l),我们定义往根走为上行,往叶子节点走为下行.那么每段路径都是由上行路径和下行路径组成的.我们分别统计答案

    首先统计上行路径,(dep[u])表示点(u)深度,对于路径(u)(l),如果这段路径对点(x)产生贡献,那么有

    (dep[u] -dep[x]=w[x]),移项得(dep[x] +w[x]=dep[u])

    然后问题变成了:把每个点看做一个"桶",把一段路径上的所有点放入数(dep[u]),最后询问每个点(x)的桶内有多少个数等于(dep[x]+w[x])

    这玩意儿可以树上差分,但是略有不同.普通的差分是直接对数进行差分,这里的差分是对桶进行差分.(可以线段树合并)

    我们在(u)点桶内放入(dep[u]),在(faz[l])桶内放入(dep[u])的相反数,那么我们对子树求和,得到的桶就是当前点(x)的桶

    关键是我们不可能开那么多桶并且暴力合并,时空复杂度都无法承受,我们考虑复用,既然我们要求的是桶(dep[x]+w[x])为止的值,我们直接把进栈和退栈时的值都求出来,求个增量就好

    然后每个点对桶进行的操作我们可以用(vector)来保存,总大小与(m)同阶

    我们再看下行路径,路径对一个点(x)产生贡献的条件

    (dep[u]-dep[l]+dep[x]-dep[x]=w[x])

    ( herefore w[x]-dep[x]=dep[u]-2 imes dep[l]),这里下标可能出现负数,开个(map)或者数组平移就好

    然后我们不能再和上行路径一样在(faz[l])处打标记,这样(lca)会被统计两次,我们在(l)出打标记即可

    下行路径:在点(v)放入(dep[u]-2 imes dep[l]),在点(l)放入(dep[u]-2 imes dep[l])相反数

    注意上下路径两个桶分别统计,不然会错统导致答案变大

    不算(Trick)(Trick),手写个类重载([])运算符,代替手动平移这样不易写挂

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <vector>
    using namespace std;
    const int maxn = 3e5 + 100,maxdep = 25;
    inline int read(){
    	int x = 0;char c = getchar();
    	while(!isdigit(c))c = getchar();
    	while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    	return x;
    }
    struct Array{
    	int val[maxn << 1];
    	int operator[](const int pos)const{return val[pos + maxn];}
    	int& operator[](const int pos){return val[pos + maxn];}
    }cnt[2];
    vector<int> G[maxn],add[2][maxn],del[2][maxn];
    inline void addedge(int from,int to){G[from].push_back(to);}
    int dep[maxn],faz[maxn][maxdep + 1],w[maxn],ans[maxn],n,m;
    inline void dfs_init(int u){
    	dep[1] = 1;
    	for(int i = 1;i <= maxdep;i++)faz[u][i] = faz[faz[u][i - 1]][i - 1];
    	for(int v : G[u]){
    		if(v == faz[u][0])continue;
    		dep[v] = dep[u] + 1;
    		faz[v][0] = u;
    		dfs_init(v);
    	}
    }
    inline int lca(int x,int y){
    	if(dep[x] < dep[y])swap(x,y);
    	for(int i = maxdep;i >= 0;i--)
    		if(dep[faz[x][i]] >= dep[y])x = faz[x][i];
    	if(x == y)return x;
    	for(int i = maxdep;i >= 0;i--)
    		if(faz[x][i] != faz[y][i])x = faz[x][i],y = faz[y][i];
    	return faz[x][0];
    }
    inline void dfs_solve(int u){
    	int tmp = cnt[0][dep[u] + w[u]] + cnt[1][w[u] - dep[u]];
    	for(int i = 0;i < 2;i++){
    		for(int x : add[i][u])cnt[i][x]++;
    		for(int x : del[i][u])cnt[i][x]--;
    	}
    	for(int v : G[u]){
    		if(v == faz[u][0])continue;
    		dfs_solve(v);
    	}
    	ans[u] = cnt[0][dep[u] + w[u]] + cnt[1][w[u] - dep[u]] - tmp;
    }
    int main(){
    	n = read(),m = read();
    	for(int u,v,i = 1;i < n;i++)u = read(),v = read(),addedge(u,v),addedge(v,u);
    	dfs_init(1);
    	for(int i = 1;i <= n;i++)w[i] = read();	
    	for(int i = 1;i <= m;i++){
    		int u = read(),v = read(),l = lca(u,v);
    		add[0][u].push_back(dep[u]);
    		del[0][faz[l][0]].push_back(dep[u]);
    		add[1][v].push_back(dep[u] - 2 * dep[l]);
    		del[1][l].push_back(dep[u] - 2 * dep[l]);
    	}
    	dfs_solve(1);
    	for(int i = 1;i <= n;i++)
    		printf("%d%c",ans[i],i == n ? '
    ' : ' ');
    	return 0;
    }
    
  • 相关阅读:
    【集合】元组元素命名
    普通数组-队列
    稀疏数组
    Java基础 07 API概述 Scanner类 Random类 ArrayList类
    Java基础 06 类与对象、封装、构造方法
    Java基础 05 数组
    Java基础 04 IDEA、方法
    Java基础 04 [附] IDEA 的安装、配置与使用
    Java基础 03 流程控制语句
    Java基础 02 数据类型转换、运算符、方法入门
  • 原文地址:https://www.cnblogs.com/colazcy/p/11693599.html
Copyright © 2011-2022 走看看