Codeforces Round 907 div2 F. A Growing Tree

F. A Growing Tree

Codeforces Round 907 div2 F. A Growing Tree_第1张图片

题意

给定一个有根树,初始只有根节点 1 1 1。每个节点都有一个点权,初始皆为 0 0 0
现有两种操作:

  • 1 v i 1\hspace{3pt}v_i 1vi,给 v i v_i vi 添加一个新的儿子,新加儿子的编号是连续累加的。
  • 2 v i x i 2\hspace{3pt}v_i\hspace{3pt}x_i 2vixi,将现在 v i v_i vi 的子树中所有点的点权 + x i +x_i +xi

q q q 次操作后输出所有点的点权

思路

离线处理所有操作。先将所有操作存起来,然后在最终的树上跑一遍 d f s dfs dfs,确定每个点的 d f s dfs dfs 序(即 d f n [ i ] dfn[i] dfn[i])。

有一个性质:如果以 u p [ i ] up[i] up[i] 表示以 i i i 为根的子树中最大的 d f n dfn dfn,那么所有 i i i 的子树中的节点 j j j,都满足 d f n [ i ] ≤ d f n [ j ] ≤ u p [ i ] dfn[i] \leq dfn[j] \leq up[i] dfn[i]dfn[j]up[i]

如果我们建立一颗 d f s dfs dfs序为下标 的线段树,那么每次操作二就等价于某个区间修改
问题是这个区间修改会修改所有 i i i 的子树中的节点,但是可能有些节点还没有被加进来。

其实对于某个点 j j j 来说,它一定是在某个时间节点被添加进来的,在这个时间节点之前的所有区间修改都跟他没有关系,那么我们新添加一个节点时,它的点权其实可能被之前的操作二影响了,并且刚好储存在线段树对应的位置中。我们只需要单点查询一下这个点的点权是多少,然后进行一次 L = R L=R L=R区间修改 就可以抵消之前的影响了。

// Problem: F. A Growing Tree
// Contest: Codeforces - Codeforces Round 907 (Div. 2)
// URL: https://codeforces.com/contest/1891/problem/F
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n' 
#define ull unsigned long long

const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;

typedef long long ll;

const int N = 500050;

std::vector<int> g[N];
int dfn[N]; //dfs序
int up[N]; //子树dfn上限
int tot; //时间戳
int pt[N]; //每个时间戳对应的点

void dfs(int u,int fa){
	dfn[u] = ++tot;
	pt[tot] = u;
	for(auto v : g[u])
		if(v != fa)
			dfs(v,u);
	up[u] = tot;
}

struct node{ //线段树以dfn序为下标,记录每个位置的点
	int ID;  //这个dfn对应的节点编号
	ll val; //点权
}tree[N<<2];

void build(int p,int l,int r){
	tree[p] = {0,0};
	if(l == r){
		tree[p].ID = pt[l];
		return;
	}
	int mid = l+r>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}

void update(int p,int l,int r,int L,int R,ll x){ //[L,R]位置val += x
	if(L <= l && r <= R){
		tree[p].val += x;
		return;
	}
	int mid = l+r>>1;
	if(L <= mid)	update(p<<1,l,mid,L,R,x);
	if(R > mid) update(p<<1|1,mid+1,r,L,R,x);
}

ll query(int p,int l,int r,int pos,ll res){ //单点修改
	res += tree[p].val;
	if(l == r)	return res;
	int mid = l+r>>1;
	if(pos <= mid) return query(p<<1,l,mid,pos,res);
	else return query(p<<1|1,mid+1,r,pos,res);
}

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin>>t;
    while(t--){
    	int q;
    	std::cin>>q;
    	int cnt = 1; //节点编号
    	std::vector<std::tuple<int,int,ll>> modify; //离线修改
    	while(q--){
    		int opt;
    		std::cin>>opt;
    		if(opt == 1){
    			int id;
    			std::cin>>id;
    			g[id].push_back(++cnt);
    			g[cnt].push_back(id);
    			modify.emplace_back(1,cnt,0);
    		}
    		else{
    			int id;
    			ll x;
    			std::cin>>id>>x;
    			modify.emplace_back(2,id,x);
    		}
    	}
    	dfs(1,0);
    	build(1,1,tot);
    	for(auto [type,id,x] : modify){
    		if(type == 1){
    			ll d = query(1,1,tot,dfn[id],0);
    			update(1,1,tot,dfn[id],dfn[id],-d);
    		}
    		else{
    			update(1,1,tot,dfn[id],up[id],x);
    		}
    	}
    	
    	std::vector<ll> ans(cnt+1,0);
    	fore(i,1,cnt+1)	ans[i] = query(1,1,tot,dfn[i],0);
    	fore(i,1,cnt+1)	std::cout<<ans[i]<<" \n"[i==cnt];
    	
    	tot = 0;
    	fore(i,1,cnt+1)	g[i].clear();
    }
	return 0; 
}

你可能感兴趣的:(codeforces,练习,深度优先,算法,c++)