ZhongHaoxi P105-3 洗衣

本文探讨了一种基于树结构的算法,通过递归和记忆化技术,高效地计算了一系列由不同参数构建的树中任意两点间的距离和。文章详细介绍了如何通过递归构建树,并利用记忆化避免重复计算,最终求得每棵树中所有点对之间的距离之和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

描述

洗完衣服,就要晒在树上。但是这个世界并没有树,我们需要重新开始造树。我们一开始拥有 $T_0$,是一棵只有一个点的树,我们要用它造出更多的树。
生成第i棵树我们需要五个参数 $a_i,b_i,c_i,d_i,l_i(a_i,b_i<i)$。我们生成第$i$棵树是将第$a_i$棵树的$c_i$号点和第$b_i$棵树的$d_i$号点用一条长度为$l_i$的边连接起来形成的新的树(不会改变原来两棵树)。下面我们需要对新树中的点重编号;对于原来在第$a_i$棵树中的点,我们不会改变他们的编号;对于原来在第$b_i$棵树中的点,我们会将他们的编号加上第$a_i$棵树的点的个数作为新的编号。
定义
$$ F(T_i) = \sum_{i=0}^{n-1} \sum_{j=i+1}^{n-1} d(v_i, v_j)$$
其中,$n$为树$T_i$的大小,$v_i,v_j$是$T_i$中的点,$d(v_i,v_j)$代表这两个点的距离。现在希望你求出$∀1≤i≤m,F(T_i)$是多少。

输入

第一行一个整数$m$,代表要造多少棵树。

接下来$m$行,每行$5$个数$a_i,b_i,c_i,d_i,l_i$。

输出

$m$行每行一个整数代表$F(T_i)$对$10^9+7$取模之后的值。

样例

输入

3
0 0 0 0 2
1 1 0 0 4
2 2 1 0 3

输出

2
28
216

约定

对于$30\%$的数据,$1≤m≤10$。

对于$60\%$的数据,每棵树的点数个数不超过$10^5$。

对于$100\%$的数据,$1≤m≤60$。

Solution

让我们用$ans_i$表示第$i$次操作的答案,不难想到$ans_i = ans_{a_i} + ans_{b_i} + \dots$

那么后面还要加什么呢?

 举个例子

 红色的为$a$,蓝色的为$b$,橙色的为这次连接的长度为$l$的边

既然两棵子树内部的答案都已经统计了,我们只需要统计跨$l$的答案即可

首先考虑$l$的贡献,因为$a$和$b$中的每一个节点都会记录一次答案,所以$l$经过了$size_a \times size_b$次

然后,我们显然可以用$\sum_{i=0}^{size_a - 1} \sum_{j = 0}^{size_b - 1} ( \operatorname{dist}(i, c) + \operatorname{dist}(j, d)) $,来表示剩余的答案,化简一下:

 $\sum_{i=0}^{size_a - 1} \sum_{j = 0}^{size_b - 1} ( \operatorname{dist}(i, c) + \operatorname{dist}(j, d)) $

$= \sum_{i=0}^{size_a - 1} (\sum_{j = 0}^{size_b - 1} \operatorname{dist}(i, c) + \operatorname{g}(b, d))$

$= \sum_{i=0}^{size_a - 1} \sum_{j = 0}^{size_b - 1} \operatorname{dist}(i, c) + \sum_{i=0}^{size_a - 1}\operatorname{g}(b, d)$

$= \sum_{j = 0}^{size_b - 1} \operatorname{g}(a, c) + \sum_{i=0}^{size_a - 1}\operatorname{g}(b, d)$

$= \operatorname{g}(a, c) \times size_b + \operatorname{g}(b, d) \times size_a$

 其中$\operatorname{g}(T, u)$表示在$T$这棵树中,$u$这个节点到其它所有节点的距离和

于是就可以愉快的计算答案啦

int main()
{
	ios::sync_with_stdio(false);
	cin >> m;
	for(register int i = 1; i <= m; i++)
	{
		cin >> a[i] >> b[i] >> c[i] >> d[i] >> l[i]; 
	}
	size[0] = 1;
	for(register int i = 1; i <= m; i++)
	{
		// 先递推 size[] 
		size[i] = (size[a[i]] + size[b[i]]) % MOD;
	}
	for(register int i = 1; i <= m; i++)
	{
		// 计算 ans[] 并输出 
		ans[i] = ((ans[a[i]] + ans[b[i]]) % MOD + size[a[i]] * size[b[i]] % MOD * l[i] % MOD) % MOD;
		ans[i] = (ans[i] + g(a[i], c[i]) * size[b[i]] % MOD + g(b[i], d[i]) * size[a[i]] % MOD) % MOD;
		cout << ans[i] << endl;
	}
	return 0;
}

怎么实现$\operatorname{g}()$呢?

递推当然是可以的,但必然需要$\mathcal{O}(n \times m)$的复杂度,而$n$却高达$2^{60}$,一种可行的方法是使用递归,只计算需要的,再加上记忆化,就变成$\log$了

 再用刚才的例子

现在我们想要计算$\operatorname{g}(^* this, c)$

其答案显然为:

是不是就等于$\operatorname{g}(a, c) + \operatorname{g}(b, d) + l \times size_b$?

那要是计算$\operatorname{g}(^* this, u)$呢?

 

 

 要是按照刚刚的方法,计算$\operatorname{g}(a, c) + \operatorname{g}(b, d) + l \times size_b$的话:

 

 漏掉的这个是什么?恰好就是$size_b \times \operatorname{dist}(a, u, c)$(这里的$ \operatorname{dist}$是指在$a$这棵子树中,$u$和$c$的距离)

于是

$$ \operatorname{g}(^* this, u)  = \operatorname{g}(a, c) + \operatorname{g}(b, d) + (l + \operatorname{dist}(a, u, c)) \times size_b $$

$u$在$b$中同理,注意$u$的编号要减去$size_a$

LL g(LL id, LL pos)
{
	// map1 是记忆化映射 
	if(!id) return 0;
	pair<LL, LL> query = make_pair(id, pos);
	if(map1.count(query)) return map1[query]; // 调用老数据 
	LL npos = pos - size[a[id]];
	// 计算并记忆化 
	if(pos < size[a[id]])
		map1[query] = ((dist(a[id], c[id], pos) + l[id]) * size[b[id]] % MOD + g(b[id], d[id]) + g(a[id], pos)) % MOD;
	else
		map1[query] = ((dist(b[id], d[id], npos) + l[id]) * size[a[id]] % MOD + g(a[id], c[id]) + g(b[id], npos)) % MOD;
	return map1[query];	
}

问题又来了,$\operatorname{dist}(T, u, v)$怎么算呢?

LCA?建树也需要时间

考虑到我们拥有完整的建树过程,而且树的种类至多$60$种,那么当然可以递归来求啦

也就是分类讨论一下

  1. 如果$u, v$在$T$的同一个子树$child$中,显然$\operatorname{dist}(T, u, v) = \operatorname{dist}(child, u, v)$
  2. 否则,就是两者到$l$的距离和加上$l$的长度,$\operatorname{dist}(T, u, v) = \operatorname{dist}(child1, u, c) + \operatorname{dist}(child2, v, d) + l$

同样注意$b$那棵树里的节点编号要减去$size_a$

LL dist(LL id, LL pos1, LL pos2)
{
	if(!id || pos1 == pos2) return 0;
	pair<LL, pair<LL, LL> > query = make_pair(id, make_pair(pos1, pos2));
	if(map2.count(query)) return map2[query];
	if(pos1 < size[a[id]]) // pos1 在 a 中 
	{
		if(pos2 < size[a[id]]) map2[query] = dist(a[id], pos1, pos2); // pos2 也在 a 中 
		else map2[query] = (dist(a[id], pos1, c[id]) + dist(b[id], pos2 - size[a[id]], d[id]) + l[id]) % MOD; // pos2 在 b 中 
	}
	else // pos1 在 b 中 
	{
		if(pos2 < size[a[id]]) map2[query] = (dist(a[id], pos2, c[id]) + dist(b[id], pos1 - size[a[id]], d[id]) + l[id]) % MOD; // pos2 在 a 中 
		else map2[query] = dist(b[id], pos1 - size[a[id]], pos2 - size[a[id]]); // pos2 在 b 中 
	}
	return map2[query];
}

上述两个函数的边界都非常显然,于是就愉快的解决了这道题

完整代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1000000007LL;
const int N = 70;
map<pair<LL, LL>, LL> map1;
map<pair<LL, pair<LL, LL> >, LL> map2;

LL m, a[N], b[N], c[N], d[N], l[N], ans[N], size[N];

LL dist(LL id, LL pos1, LL pos2)
{
	if(!id || pos1 == pos2) return 0;
	pair<LL, pair<LL, LL> > query = make_pair(id, make_pair(pos1, pos2));
	if(map2.count(query)) return map2[query];
	if(pos1 < size[a[id]]) // pos1 在 a 中 
	{
		if(pos2 < size[a[id]]) map2[query] = dist(a[id], pos1, pos2); // pos2 也在 a 中 
		else map2[query] = (dist(a[id], pos1, c[id]) + dist(b[id], pos2 - size[a[id]], d[id]) + l[id]) % MOD; // pos2 在 b 中 
	}
	else // pos1 在 b 中 
	{
		if(pos2 < size[a[id]]) map2[query] = (dist(a[id], pos2, c[id]) + dist(b[id], pos1 - size[a[id]], d[id]) + l[id]) % MOD; // pos2 在 a 中 
		else map2[query] = dist(b[id], pos1 - size[a[id]], pos2 - size[a[id]]); // pos2 在 b 中 
	}
	return map2[query];
}
LL g(LL id, LL pos)
{
	// map1 是记忆化映射 
	if(!id) return 0;
	pair<LL, LL> query = make_pair(id, pos);
	if(map1.count(query)) return map1[query]; // 调用老数据 
	LL npos = pos - size[a[id]];
	// 计算并记忆化 
	if(pos < size[a[id]])
		map1[query] = ((dist(a[id], c[id], pos) + l[id]) * size[b[id]] % MOD + g(b[id], d[id]) + g(a[id], pos)) % MOD;
	else
		map1[query] = ((dist(b[id], d[id], npos) + l[id]) * size[a[id]] % MOD + g(a[id], c[id]) + g(b[id], npos)) % MOD;
	return map1[query];	
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> m;
	for(register int i = 1; i <= m; i++)
	{
		cin >> a[i] >> b[i] >> c[i] >> d[i] >> l[i]; 
	}
	size[0] = 1;
	for(register int i = 1; i <= m; i++)
	{
		// 先递推 size[] 
		size[i] = (size[a[i]] + size[b[i]]) % MOD;
	}
	for(register int i = 1; i <= m; i++)
	{
		// 计算 ans[] 并输出 
		ans[i] = ((ans[a[i]] + ans[b[i]]) % MOD + size[a[i]] * size[b[i]] % MOD * l[i] % MOD) % MOD;
		ans[i] = (ans[i] + g(a[i], c[i]) * size[b[i]] % MOD + g(b[i], d[i]) * size[a[i]] % MOD) % MOD;
		cout << ans[i] << endl;
	}
	return 0;
}

  

 

转载于:https://www.cnblogs.com/syksykCCC/p/ZHXP105-3.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值