HDU 4705 立方和拆解

HDU 4705 立方和拆解

题意

给一棵树,找出所有的(A,B,C)三元组的数量,满足ABC不能同时被一条路径覆盖

思路

  • 首先可以明确的是,对于每个入度大于等于3的点(也就是有两个或以上儿子的节点),对答案都有贡献,并且这个贡献是任意三个与其相连的部分的权值乘积的加和。(由于每个节点的“分叉位置”不用,所以不会出现重复记录的现象)

  • 也就是说,从根节点(我们取1节点为根)开始,进行到一棵以某节点为根的子树时,设这棵子树权值为 q 0 q_0 q0, 而此节点下面的子树权值为 q 1 , q 2 ⋅ ⋅ ⋅ q s q_1, q_2 ··· q_s q1,q2qs, 我们要算的就是

∑ i = 0 s − 2 ∑ j = i + 1 s − 1 ∑ k = j + 1 s q i ∗ q j ∗ q k \sum_{i = 0}^{s - 2}{\sum_{j = i + 1}^{s - 1}{\sum_{k = j + 1}^{s}{q_i * q_j * q_k}}} i=0s2j=i+1s1k=j+1sqiqjqk

  • 但是我们肯定不能n^3来算,这个时候可以模仿序列和的平方化任意两数乘积加和的公式:

( ( ∑ i = 0 s q i ) 2 − ∑ i = 0 s q i 2 ) / 2 = ∑ i = 0 s − 1 ∑ j = i + 1 s q i ∗ q j ((\sum_{i = 0}^{s}{q_i})^2 - \sum_{i = 0}^{s}{q_i ^ 2}) / 2 = \sum_{i = 0}^{s - 1}{\sum_{j = i + 1}^{s}{q_i * q_j}} ((i=0sqi)2i=0sqi2)/2=i=0s1j=i+1sqiqj

来写出序列和的立方化任意三数乘积加和的公式:

( ( ∑ i = 0 s q i ) 3 − 3 ∗ ∑ i = 0 s q i 2 ∗ ∑ i = 0 s q i + 2 ∗ ∑ i = 0 s q i 3 ) / 6 = ∑ i = 0 s − 2 ∑ j = i + 1 s − 1 ∑ k = j + 1 s q i ∗ q j ∗ q k ((\sum_{i = 0}^{s}{q_i})^3 - 3 * \sum_{i = 0}^{s}{q_i ^ 2} *\sum_{i = 0}^{s}{q_i} + 2 * \sum_{i = 0}^{s}{q_i ^ 3}) / 6 = \sum_{i = 0}^{s - 2}{\sum_{j = i + 1}^{s - 1}{\sum_{k = j + 1}^{s}{q_i * q_j * q_k}}} ((i=0sqi)33i=0sqi2i=0sqi+2i=0sqi3)/6=i=0s2j=i+1s1k=j+1sqiqjqk

这样就可以O(n)搜索算出答案了

AC代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

int n;
int vv[200005], nex[200005], fir[100005];
long long sz[100005];
long long ans = 0;

void dfs1(int o, int fa)
{
	sz[o] = 1;
	for (int i = fir[o]; i; i = nex[i])
	{
		if (vv[i] != fa)
		{
			dfs1(vv[i], o);
			sz[o] += sz[vv[i]];
		}
	}
}

void dfs(int o, int fa)
{
	long long ext = n - sz[o];
	long long c3 = ext * ext * ext;
	long long c2 = ext * ext;
	long long c1 = ext;
	int cnt = 0;
	for (int i = fir[o]; i; i = nex[i])
	{
		if (vv[i] != fa)
		{
			++cnt;
			c3 += sz[vv[i]] * sz[vv[i]] * sz[vv[i]];
			c2 += sz[vv[i]] * sz[vv[i]];
			c1 += sz[vv[i]];
			dfs(vv[i], o);
		}
	}
	if (cnt >= 2)
	{
		ans += (c1 * c1 * c1 - 3 * c2 * c1 + 2 * c3) / 6;
	}
}

int main()
{
	while (scanf("%d", &n) == 1)
	{
		memset(nex, 0, sizeof(int) * (2 * n + 2));
		memset(fir, 0, sizeof(int) * (n + 2));
		memset(sz, 0, sizeof(long long) * (n + 2));
		ans = 0;
		for (int i = 1; i < n; ++i)
		{
			int u;
			scanf("%d%d", &u, &vv[i]);
			vv[i + n] = u;
			nex[i] = fir[u];
			fir[u] = i;
			nex[i + n] = fir[vv[i]];
			fir[vv[i]] = i + n;
		}
		dfs1(1, 0);
		dfs(1, 0);
		printf("%lld\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值