2024杭电钉耙1-1003 HDOJ7435 树

本文同步发布于我的网站

Problem

给一棵根为 1 的有根树,点 i i i 具有一个权值 A i A_i Ai

定义一个点对的值 f ( u , v ) = max ⁡ ( A u , A v ) × ∣ A u − A v ∣ f(u, v)=\max \left(A_u, A_v\right) \times\left|A_u-A_v\right| f(u,v)=max(Au,Av)×AuAv

你需要对于每个节点 i i i ,计算 a n s i = ∑ u ∈ subtree ⁡ ( i ) , v ∈ subtree ⁡ ( i ) f ( u , v ) a n s_i=\sum_{u \in \operatorname{subtree}(i), v \in \operatorname{subtree}(i)} f(u, v) ansi=usubtree(i),vsubtree(i)f(u,v) ,其中 subtree ⁡ ( i ) \operatorname{subtree}(i) subtree(i) 表示 i i i 的子树。

请你输出 ⊕ ( a n s i   m o d   2 64 ) \oplus\left(a n s_i \bmod 2^{64}\right) (ansimod264) ,其中 ⊕ \oplus 表示 XOR。

n ≤ 5 × 1 0 5 , 1 ≤ A i ≤ 1 0 6 n \leq 5 \times 10^5, 1 \leq A_i \leq 10^6 n5×105,1Ai106

Solution

先来愉快的推式子。

其实 max ⁡ ( A u , A v ) × ∣ A u − A v ∣ \max \left(A_u, A_v\right) \times\left|A_u-A_v\right| max(Au,Av)×AuAv 其实就是 max ⁡ 2 − max ⁡ ⋅ min ⁡ \max^2-\max \cdot \min max2maxmin,这两部分可以分开思考。

对于 max ⁡ ⋅ min ⁡ \max\cdot\min maxmin,其实就是在 i i i 的子树内任选两个点 u , v ∈ subtree ⁡ ( i ) u,v\in \operatorname{subtree}(i) u,vsubtree(i) 相乘

∑ u ∑ v A u × A v = ∑ u A u ∑ v A v = ( ∑ u A u ) 2 \begin{align} &\sum_{u} \sum_{v } A_u\times A_v\\ =&\sum_{u }A_u\sum_{v}A_v\\ =&(\sum_{u}A_u)^2 \end{align} ==uvAu×AvuAuvAv(uAu)2
对于 max ⁡ 2 \max ^2 max2,也就是 ∑ u , v ∈ subtree ⁡ ( i ) ( max ⁡ ( A u , A v ) ) 2 \sum_{u,v\in \operatorname{subtree}(i)}(\max(A_u,A_v))^2 u,vsubtree(i)(max(Au,Av))2,我们需要思考子树合并的情况。

假设我们已经计算了节点 u u u 的所有子节点的子树的内部信息, v v v u u u 的某个儿子,此时我们需要计算

  • u u u subtree ⁡ ( v ) \operatorname{subtree}(v) subtree(v) 之间的贡献
  • subtree ⁡ ( v i ) \operatorname{subtree}(v_i) subtree(vi) subtree ⁡ ( v j ) \operatorname{subtree}(v_j) subtree(vj) 之间的贡献(即跨点 u u u 的两点之间的贡献)

我们按照以下方式合并的同时计算贡献(以下步骤来自于题解)

  • subtree ⁡ ( u ) \operatorname{subtree}(u) subtree(u) 初始为 { u } \{u\} {u}
  • 计算 subtree ⁡ ( v ) \operatorname{subtree}(v) subtree(v) 和当前 subtree ⁡ ( u ) \operatorname{subtree}(u) subtree(u) 之间点对的答案。(跨越 u u u 节点的部分)。
  • subtree ⁡ ( v ) \operatorname{subtree}(v) subtree(v) 子树内的答案直接累加。(不跨越 u u u 节点的部分)。
  • subtree ⁡ ( u ) ← subtree ⁡ ( u ) + subtree ⁡ ( v ) \operatorname{subtree}(u) \leftarrow \operatorname{subtree}(u)+\operatorname{subtree}(v) subtree(u)subtree(u)+subtree(v) (将 v v v 的子树加入到 u u u 中)。

我们需要维护两个变量:一个子树内的权值出现次数 c n t cnt cnt 与权值平方和 s u m sum sum

当前子树 subtree ⁡ ( u ) \operatorname{subtree}(u) subtree(u) 内加入一个权重为 w w w 的点,对于答案贡献多少呢?

  • 对于 subtree ⁡ ( u ) \operatorname{subtree}(u) subtree(u) 中每个权值小于 w w w 的点,贡献 1 × w 2 1\times w^2 1×w2,总计 2 × ∑ i = 1 w − 1 c n t i × w 2 2\times\sum_{i=1}^{w-1} cnt_i\times w^2 2×i=1w1cnti×w2

  • 对于 subtree ⁡ ( u ) \operatorname{subtree}(u) subtree(u) 中每个权值大于等于 w w w 的点(权重为 w ′ w^\prime w),贡献 1 × w ′ 2 1\times {w^\prime}^2 1×w2,总计 2 × ∑ i = w 1 0 6 s u m i 2\times\sum_{i=w}^{10^6}sum_i 2×i=w106sumi

对于每个节点,我们开一颗线段树。初始时,每个节点的线段树只包含其本身点权。计算完某个点所有儿子的 a n s ans ans 之后,我们将所有儿子的线段树合并到其自己上,同时计算贡献。

Code

#define N 500010
#define M 1000000
ULL a[N];
int n;

namespace Tree
{
	int head[N],nxt[N*2],ver[N*2],f[N];
	int cnt;
	void insert(int x,int y)
	{
		nxt[++cnt]=head[x];
		head[x]=cnt;
		ver[cnt]=y;
	}
	
};

using Tree::insert;
using Tree::head;
using Tree::nxt;
using Tree::ver;

namespace Seg
{
	struct Node
	{
		int ls,rs,l,r;
		ULL sum,cnt;
#define ls(x) a[x].ls
#define rs(x) a[x].rs
#define l(x) a[x].l
#define r(x) a[x].r
#define sum(x) a[x].sum
#define cnt(x) a[x].cnt
	}a[N*40];
	int cnt;
	int root[N];
	int new_node(int l,int r)
	{
		cnt++;
		l(cnt)=l;
		r(cnt)=r;
		return cnt;
	}
	
	void add(int &p,int x)
	{
		debug
		if(p==0) p=new_node(1,M);
		if(l(p)==r(p))
		{
			debug
			sum(p)+=(ULL)(x)*x;
			cnt(p)++;
			return;
		}
		int mid=(l(p)+r(p))/2;
		if(x<=mid)
		{
			if(!ls(p)) ls(p)=new_node(l(p),mid);
			add(ls(p),x);
		}
		else
		{
			if(!rs(p)) rs(p)=new_node(mid+1,r(p));
			add(rs(p),x);
		}
		cnt(p)=cnt(ls(p))+cnt(rs(p));
		sum(p)=sum(ls(p))+sum(rs(p));
	}
	
	int merge(int x,int y,ULL &ans)
	{
		if(!x) return y;
		if(!y) return x;
		if(l(x)==r(x))
		{
			ans+=2*cnt(x)*sum(y);
			sum(x)+=sum(y);
			cnt(x)+=cnt(y);
			return x;
		}
		
		sum(x)+=sum(y);
		cnt(x)+=cnt(y);
		
		ans+=2*cnt(ls(x))*sum(rs(y));
		ans+=2*cnt(ls(y))*sum(rs(x));
		
		ls(x)=merge(ls(x),ls(y),ans);
		rs(x)=merge(rs(x),rs(y),ans);
		
		return x;
	}
	
};

using Seg::add;
using Seg::merge;
using Seg::root;

ULL sq[N],ans[N],sum[N];

void dfs(int x,int f)
{
	sum[x]=a[x];
	sq[x]=a[x]*a[x];
	add(root[x],a[x]);
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==f) continue;
		dfs(y,x);
		sum[x]+=sum[y];
		sq[x]+=sq[y];
		root[x]=merge(root[x],root[y],sq[x]);
	}
	ans[x]=sq[x]-sum[x]*sum[x];
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cout.precision(10);
	int t=1;
//	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<n;i++)
		{
			int x,y;
			cin>>x>>y;
			Tree::insert(x,y);
			Tree::insert(y,x);
		}
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		
		dfs(1,0);
		
		ULL out=0;
		for(int i=1;i<=n;i++)
		{
			out^=ans[i];
//			cout<<ans[i]<<" ";
		}
		cout<<out<<endl;
		
	}
	return 0;
}
  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值