三角果计数

传送门

题目描述#

给一个n 个节点的树,三角果定义为一个包含 3 个节点的集合,且他们两两之间的最短路长度 a,b,c 能够构成一个三角形。

计算这棵树上有多少个不同的三角果。

两个三角果不同当且仅当 a1,b1,c1!=a2,b2,c2

思路#

简单画一下会发现当三个点在一条线上时一定会发生两边之和等于第三遍的情况。其他的情况一定是满足条件的.

一种较为容易想到的做法是,用 dfs 进行树的遍历,枚举到每个节点时我们算出 不包含这个节点的所有方案,即把这个点

root 当做树根,去计算所有的合法方案。 由于树上两点 u,v 的最短路径一定是 ulca(u,v)v 因此我们只需要

root 的三个不同的子树任选一个点,即可构成一个合法方案。 因为在某个子树中选择多个节点会在 dfs 到子树的时候会求出,这样可以避免重复计算。 (并且在此题中边权没有用)
(由于每次以root为根的计算方案数的时候,三个点的 lca 均为 root,以此进行区分可以得出不重不漏)

上述问题可以等价为以每个点为根,在其所有子树中选择三个不同的子树,每个子树选择一个节点的方案数之和

ans=i=1nj=i+1nk=j+1nai×aj×ak

立方和公式中

(a+b+c)3=a3+b3+c3+3a2b+3a2c+2b2a+3b2c+3c2a+3c2b+6abc

因此我们可以转化一下等式

sum=i=1naians=sum3i=1nai33×i=1nai2×(sumai)6

CODE
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i ++ )

using namespace std;

typedef long long LL;
typedef pair<int, int> PII ;

template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }

const int N = 1e5 + 10, M = 2e5 + 10;

int h[N], ne[M], e[M], idx;
int sz[N];
int n; 

void add(int a, int b) {
  e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

LL dfs(int u, int fa) {

  sz[u] = 1;
  LL ans = 0, sum1 = 0, sum2 = 0, sum = n - 1;

  vector<int> son;
  
  for(int i = h[u]; ~i; i = ne[i]) {
  	int j = e[i];
  	if(j == fa) continue;
  	ans += dfs(j, u);
  	sz[u] += sz[j];
  	son.push_back(sz[j]);
  }

  if(n - sz[u]) {
	son.push_back(n - sz[u]);	
  }
	
  if(son.size() < 3) return ans;
  for(auto x: son) {
	sum2 += 3LL * x * x * (sum - x);
	sum2 += 1LL * x * x * x;
  }

  ans += (sum * sum * sum - sum1 - sum2) / 6;
  return ans;
}



int main() {
  scanf("%d", &n);
  rep(i, 0, n) h[i] = -1;
  rep(i, 1, n - 1) {
  	int u, v, w; scanf("%d%d%d", &u, &v, &w);
  	add(u, v); add(v, u);
  }

  dfs(1, -1);
  printf("%lld", ans);
  return 0;
}


对此问题的第二种解决方案
我们再对问题进行一下抽象
对于每个点为 root , 我们有 n 个子树,每个子树的节点个数是 ai

ans=i=1nj=i+1nk=j+1nai×aj×ak

我们一定不能选择重复的子树,因此我们可以枚举 aj,进行计算,这样我们左边可选方案数就是
i=1j1ai 右边可选方案数就是 k=j+1nak
我们在进行 dfs 遍历 root 的所有儿子的时候,可以维护一个前缀计算的和 pre,很显然 pre=sz[root]sz[root] 是当前已经枚举过的所有子树的 ai 的和,而后缀 sam=n1presz[j] , j 是当前节点.

CODE
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i ++ )

using namespace std;

typedef long long LL;
typedef pair<int, int> PII ;

template <typename T> void chkmax(T &x, T y) { x = max(x, y); }
template <typename T> void chkmin(T &x, T y) { x = min(x, y); }

const int N = 1e5 + 10, M = 2e5 + 10;

int h[N], ne[M], e[M], idx;
int sz[N];
int n; 
LL ans;

void add(int a, int b) {
  e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void dfs(int u, int fa) {
  for(int i = h[u]; ~i; i = ne[i]) {
  	int j = e[i];
  	if(j == fa) continue;
  	dfs(j, u);
  	ans += 1LL * sz[u] * sz[j] * (n - sz[u] - sz[j] - 1);
  	sz[u] += sz[j];
  }
  sz[u] ++;
}

int main() {
  scanf("%d", &n);
  rep(i, 0, n) h[i] = -1;
  rep(i, 1, n - 1) {
  	int u, v, w; scanf("%d%d%d", &u, &v, &w);
  	add(u, v); add(v, u);
  }

  dfs(1, -1);

  printf("%lld", ans);

  return 0;
}

posted @   ccz9729  阅读(189)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
主题色彩