2019 CCPC Camp day2 F.采蘑菇的克拉莉丝(树链剖分 + 思维)

在这里插入图片描述


容易想到比较直接暴力的方法:用线段树维护子树和,加入蘑菇时,更新从 u 到 根节点路径上的所有子树和,这个可以用树剖实现。查询时 枚举 当前起点的所有出度点,计算贡献。这种做法在一般数据下是 n log ⁡ 2 n n \log^2 n nlog2n,可以被菊花图卡成 n 2 log ⁡ n n^2 \log n n2logn

考虑如何优化:
在树剖的更新时,两条重链之间隔着一条轻边,当向上跳经过这条轻儿子边时,顺便维护这条轻儿子边对其父节点的贡献。统计答案时,每个点只枚举重儿子和父亲节点的子树和来计算贡献,轻儿子的答案在树剖维护子树和时直接维护。

复杂度: O ( n log ⁡ 2 n ) O(n \log^2 n) O(nlog2n)


代码:

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
const int maxn = 1e6 + 10;
typedef long long ll;
int n,q;
ll ans[maxn];
struct seg_tree {
	#define lson rt << 1,l,mid
	#define rson rt << 1 | 1,mid + 1,r
	ll sum[maxn << 2];
	void build(int rt,int l,int r) {
		sum[rt] = 0;
		if(l == r) return ;
		int mid = l + r >> 1;
		build(lson); build(rson);
	}
	void update(int L,int R,int v,int rt,int l,int r) {
		if(L <= l && r <= R) {
			sum[rt] += v;
			return ;
		}
		int mid = l + r >> 1;
		if(L <= mid) update(L,R,v,lson);
		if(mid + 1 <= R) update(L,R,v,rson);
	}
	ll qry(int pos,int rt,int l,int r) {
		ll ans = sum[rt];
		if(l == r) return ans;
		int mid = l + r >> 1;
		if(pos <= mid) return ans + qry(pos,lson);
		else return ans + qry(pos,rson);
		return ans;
	}
};
struct Tree{
	int dep[maxn],son[maxn],siz[maxn],dfn[maxn],top[maxn],cnt,p[maxn],pw[maxn];
	vector<pii> g[maxn];	
	seg_tree seg;
	void add(int u,int v,int w) {						//加边 
		g[u].push_back(pii(v,w));
		g[v].push_back(pii(u,w));
	}
	void prework(int u,int fa) {						//预处理 
		son[u] = 0; siz[u] = 1;
		if(u == 1) dep[u] = 1;
		for(auto it : g[u]) {
			if(it.fir == fa) continue;
			p[it.fir] = u;
			pw[it.fir] = it.sec;
			dep[it.fir] = dep[u] + 1;
			prework(it.fir,u);
			siz[u] += siz[it.fir];
			if(!son[u] || siz[it.fir] > siz[son[u]])
				son[u] = it.fir;
		}
	}
	void dfs(int u,int tp) {							//得到重儿子和链顶 
		dfn[u] = ++cnt; top[u] = tp;
		if(!son[u]) return;
		dfs(son[u],tp);
		for(auto it : g[u]) {
			if(it.fir == p[u] || it.fir == son[u]) continue;
			dfs(it.fir,it.fir);
		}
	}
	void modify(int u,int v,int k) {					//树上路径更新 
		while(top[u] != top[v]) {
			if(dep[top[u]] < dep[top[v]]) swap(u,v);
			seg.update(dfn[top[u]],dfn[u],k,1,1,n);
			ans[p[top[u]]] += 1ll * pw[top[u]] * k;
			u = p[top[u]];
		}
		if(dep[u] > dep[v]) swap(u,v);
		seg.update(dfn[u],dfn[v],k,1,1,n);
	}
}tree;

int main() {
	scanf("%d",&n);
	tree.seg.build(1,1,n);
	for(int i = 1,u,v,w; i < n; i++) {
		scanf("%d%d%d",&u,&v,&w);
		tree.add(u,v,w);
	}
	tree.prework(1,0);
	tree.dfs(1,1);
	scanf("%d",&q);
	int rt = 1;
	ll tot = 0;
	while(q--) {
		int op,x,y;
		scanf("%d",&op);
		if(op == 1) {
			scanf("%d%d",&x,&y);
			tree.modify(1,x,y);
			tot += y; 
		} else {
			scanf("%d",&x);
			rt = x;
		}
		ll sum = ans[rt];
		sum += 1ll * (tot - tree.seg.qry(tree.dfn[rt],1,1,n)) * tree.pw[rt];
		if(tree.son[rt])
			sum += 1ll * (tree.seg.qry(tree.dfn[tree.son[rt]],1,1,n)) * tree.pw[tree.son[rt]];
		printf("%lld\n",sum);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值