树形DP+树上路径贡献
题目
一棵树有n个节点,每个节点都同有一个符号+或者-,表示该节点是正极节点还是负极节点。如果从正极走到负极,或者从负极走到正极,都会受到1点伤害。
现在需要知道走过所有路径后会受到的总伤害是多少?树上任意2点存在唯一的路径。需要计算所有任意2点的路径的伤害和。
注意:从u到,和从v到u视为同一条路径。
输入描述
第一行输入一个整数n(1≤n ≤ 10^5 )
第二行输入一个长度为1的仅由+和-组成的字符串。
接下来n-1 行,每行输入两个整数 u,v(1≤u,v≤n) 表示节点
u和节点v之间有一条边。
输出描述
输出一个整数表示受到的总伤害。
样例1
输入
3
++-
1 2
1 3
输出
2
说明
路径1-2 (2-1) 不会受到伤害。
脂径1-3(3-1) 会受到1点伤害。
路径2-1-3(3-1-2)会受到1点害。
因此答案为2。
题目分析
典型的树形DP,换根可以解决路径去重的问题。现在考虑怎么计算贡献。
我们可以很容易想到一个f[i]: i的子树上所有经过点i的路径的伤害值的和。那么就是答案
在DFS时到1的时候,考虑1-3的边,在考虑2和3的子树合并的时候计算f[1], 当前增加的路径是,根(1,2,4,5)到3点所有子树(3, 6)的路径。因为每条路径一定会经过1。 对每条路径进行拆分,
X是(1,2,4,5),Y是(3, 6)。
每个X会和每个Y匹配出一条路径,那么对于X-1会被计算Y次。对于每个根u,我们求出它的字树到根u的路径和
那么这个公式
转化成
f1[to]在计算前,应该处理u-to这条边的伤害。
代码如下:
#include <iostream>
using namespace std;
char ch[100005];
bool vis[100005];
vector<int> G[100005];
long long f[100005], f1[100005], son[100005];
void Dfs(int u, int fa){
son[u] = 1;
for (auto to : G[u]) {
if (to != fa) {
Dfs(to, u);
// 处理边u-to的伤害
if (ch[u] != ch[to]) {
f1[to] += son[to];
}
// 计算贡献
f[u] += f1[u] * son[to] + f1[to] * son[u];
// 换根
f1[u] += f1[to];
son[u] += son[to];
}
}
}
int main() {
int n; scanf("%d", &n);
scanf("%s", ch+1);
for(int i=1; i<n; i++) {
int a, b; scanf("%d%d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
long long ans = 0;
Dfs(1, 0);
for (int i=1; i<=n; i++) {
ans += f[i];
}
printf("%lld\n", ans);
return 0;
}
/*
7
++--+-+
1 2
2 3
2 4
3 5
1 6
6 7
5
+-++-
1 2
2 3
2 4
1 5
3
+--
1 2
1 3
8
+-++-+-+
1 2
2 3
3 4
3 5
1 6
6 7
6 8
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了