HDU 4705 立方和拆解
HDU 4705 立方和拆解
题意
给一棵树,找出所有的(A,B,C)三元组的数量,满足ABC不能同时被一条路径覆盖
思路
-
首先可以明确的是,对于每个入度大于等于3的点(也就是有两个或以上儿子的节点),对答案都有贡献,并且这个贡献是任意三个与其相连的部分的权值乘积的加和。(由于每个节点的“分叉位置”不用,所以不会出现重复记录的现象)
-
也就是说,从根节点(我们取1节点为根)开始,进行到一棵以某节点为根的子树时,设这棵子树权值为 \(q_0\), 而此节点下面的子树权值为 \(q_1, q_2 ··· q_s\), 我们要算的就是
\[\sum_{i = 0}^{s - 2}{\sum_{j = i + 1}^{s - 1}{\sum_{k = j + 1}^{s}{q_i * q_j * q_k}}}
\]
- 但是我们肯定不能n^3来算,这个时候可以模仿序列和的平方化任意两数乘积加和的公式:
\[((\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}}
\]
来写出序列和的立方化任意三数乘积加和的公式:
\[((\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}}}
\]
这样就可以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;
}