题目链接

问题描述

给定一棵树,树中每个结点权值为[-100,100]之间的整数。树中包含结点总数不超过1e5。任选两个非根节点A、B,将这两个结点与其父节点断开,可以得到三棵子树。现要求三棵子树的权值之和相等,问A、B有多少种选择方法。

输入

T:样例种数
N:树中结点个数
v1 father1
v2 father2

问题分析

此问题是一道树形DP。

如何才能将一棵树划分成三棵权值之和相等的子树?有两种情况:

  • 若结点x和结点y没有血缘关系(不是祖孙关系),则x和y的权值之和都是s(s为整棵树的权值之和的三分之一),x和y把这棵树截为三段。
  • 若结点x和结点y有血缘关系,不妨设x是y的祖先,则x的权值之和为2s,y的权值之和为s。x和y把这棵树截为三段。

最精简的代码

#include<iostream>
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn = 1e5 + 7;
typedef long long ll;
struct Node {
int v;
int s;
int son;
}a[maxn];
int nex[maxn];
int root;
int per;
int n;
ll ans;
int perCount = 0;
int go(int nodeId) {
int temp = perCount;
a[nodeId].s = a[nodeId].v;
for (int i = a[nodeId].son; i != -1; i = nex[i]) {
a[nodeId].s += go(i);
}
if (nodeId != root) {
if (a[nodeId].s == per * 2) {
ans += perCount - temp;
}
if (a[nodeId].s == per) {
ans += temp;
perCount++;
}
}
return a[nodeId].s;
}
void push(int parent, int son) {
int temp = a[parent].son;
nex[son] = temp;
a[parent].son = son;
}
int main() {
int T;
cin >> T;
while (T-- > 0) {
cin >> n;
for (int i = 0; i <= n; i++) {
a[i].son = -1;
nex[i] = -1;
}
int s = 0;
for (int i = 0; i < n; i++) {
int father;
cin >> a[i].v >> father;
s += a[i].v;
father--;
if (father == -1) {
root = i;
}
else {
push(father, i);
}
} if (s % 3 == 0) {
per = s / 3;
ans = 0;
perCount = 0;
go(root);
cout << ans << endl;
}
else {
cout << 0 << endl;
}
}
return 0;
}

复杂但是直观的代码

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
const int maxn = 1e5 + 7;
typedef long long ll;
int n;
int per;//每个分支应该等于的数值
struct Node {
int v;//结点权重
int s;//结点所代表的子树的权重之和
int sonPerCount;//子树中权值之和为per的结点个数(包括自身)
int son;//儿子结点,链表第一个结点
int next;//下一个兄弟结点
}a[maxn];
void push(int father, int son) {
int temp = a[father].son;
a[son].next = temp;
a[father].son = son;
}
int root;
void init(int nodeId) {
a[nodeId].s = a[nodeId].v;
for (int i = a[nodeId].son; ~i; i = a[i].next) {
init(i);
a[nodeId].s += a[i].s;
a[nodeId].sonPerCount += a[i].sonPerCount;
}
if (a[nodeId].s == per) {
a[nodeId].sonPerCount++;
}
}
ll count1 = 0, count2 = 0;//count1表示我的值为per时,count2表示我的值为2per时
int totalPer = 0;
int cnt = 0;
ll ans = 0;
void go(int nodeId) {
if (a[nodeId].s == per) {
if (nodeId != root) {
count1 += totalPer - cnt - a[nodeId].sonPerCount;
}
cnt++;
}
//此处不能有else,因为当per=0时,子树中的总和为per的结点也会生效
if (a[nodeId].s == per * 2) {
if (nodeId != root) {
count2 += a[nodeId].sonPerCount;
if (per == 0)count2--;//因为sonPerCount包括结点自身,所以需要先去掉结点
}
}
for (int i = a[nodeId].son; ~i; i = a[i].next) {
go(i);
}
if (a[nodeId].s == per)cnt--;
}
int main() {
freopen("in.txt", "r", stdin);
int T; cin >> T;
while (T--) {
cin >> n;
int s = 0;
for (int i = 1; i <= n; i++) {
a[i].sonPerCount = 0;
a[i].son = -1;
a[i].next = -1;
}
for (int i = 1; i <= n; i++) {
int father;
cin >> a[i].v >> father;
if (father == 0) {
root = i;
}
else {
push(father, i);
}
s += a[i].v;
}
if (s % 3 == 0) {
per = s / 3;
init(root);
count1 = count2 = 0;
totalPer = 0;
for (int i = 1; i <= n; i++) {
if (a[i].s == per)totalPer++;
}
go(root);
ans = count1 / 2 + count2;
cout << ans << endl;
}
else { cout << 0 << endl; }
}
return 0;
}

注意事项

  • 如果用Java写,此题会超时。因为输入量比较大
  • 如果数据充分一些,1e5的复杂度有可能爆栈,所以需要使用栈的方式来遍历树(也可以先对树进行线索化)。

hihocoder第237周:三等分带权树的更多相关文章

  1. 直径上的乱搞 bzoj1999求树直径上的结点+单调队列,bzoj1912负权树求直径+求直径边

    直径上的乱搞一般要求出这条直径上的点集或者边集 bzoj1999:对直径上的点集进行操作 /* 给出一颗树,在树的直径上截取长度不超过s的路径 定义点u到s的距离为u到s的最短路径长度 定义s的偏心距 ...

  2. Housewife Wind(边权树链剖分)

    Housewife Wind http://poj.org/problem?id=2763 Time Limit: 4000MS   Memory Limit: 65536K Total Submis ...

  3. BZOJ.4034 [HAOI2015]树上操作 ( 点权树链剖分 线段树 )

    BZOJ.4034 [HAOI2015]树上操作 ( 点权树链剖分 线段树 ) 题意分析 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 ...

  4. POJ.2763 Housewife Wind ( 边权树链剖分 线段树维护区间和 )

    POJ.2763 Housewife Wind ( 边权树链剖分 线段树维护区间和 ) 题意分析 给出n个点,m个询问,和当前位置pos. 先给出n-1条边,u->v以及边权w. 然后有m个询问 ...

  5. hihoCoder 第136周 优化延迟(二分答案+手写堆)

    题目1 : 优化延迟 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho编写了一个处理数据包的程序.程序的输入是一个包含N个数据包的序列.每个数据包根据其重要程度不同 ...

  6. HihoCoder第三周与POJ2406:KMP算法总结

    HihoCoder第三周: 输入 第一行一个整数N,表示测试数据组数. 接下来的N*2行,每两行表示一个测试数据.在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不 ...

  7. DFS序+线段树 hihoCoder 1381 Little Y's Tree(树的连通块的直径和)

    题目链接 #1381 : Little Y's Tree 时间限制:24000ms 单点时限:4000ms 内存限制:512MB 描述 小Y有一棵n个节点的树,每条边都有正的边权. 小J有q个询问,每 ...

  8. hihocoder第42周 3*N骨牌覆盖(状态dp+矩阵快速幂)

    http://hihocoder.com/contest/hiho42/problem/1 给定一个n,问我们3*n的矩阵有多少种覆盖的方法 第41周做的骨牌覆盖是2*n的,状态转移方程是dp[i] ...

  9. hihoCoder 1145 幻想乡的日常(树状数组 + 离线处理)

    http://hihocoder.com/problemset/problem/1145?sid=1244164 题意: 幻想乡一共有n处居所,编号从1到n.这些居所被n-1条边连起来,形成了一个树形 ...

随机推荐

  1. VMvare虚拟机如何删除安装的ubuntu操作系统

    VMvare虚拟机如何删除安装的ubuntu操作系统呢??? 这个问题其实在我刚开始接触虚拟机和ubuntu操作系统的时候对于如何删除操作系统是一件很苦恼的事情,因为按照书本的步骤,根本看不懂如何操作 ...

  2. S2750&S5700&S6700 V200R003(C00&C02&C10) MIB参考

    https://support.huawei.com/enterprise/docinforeader.action?contentId=DOC1000027337&idPath=791971 ...

  3. Python yaml处理

    安装方式: pip install pyyaml 一.module.yaml为 name: Tom Smith age: 37 spouse: name: Jane Smith age: 25 chi ...

  4. BZOJ1477 青蛙的约会 扩展欧几里德

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1477 题意概括 两只青蛙,现在分别在x,y的位置,以m,n的速度在周长为L的环形跑道上面跑. 问他 ...

  5. 内置函数 filter zip map

    1. 基本内置函数: 2. enumerate :  枚举 把列表转化为有索引的字典: 3. eval 和 exec 4. 过滤函数  filter 5. map 函数批量修改: 6. 配对函数 zi ...

  6. POJ 3903 Stock Exchange 【最长上升子序列】模板题

    <题目链接> 题目大意: 裸的DP最长上升子序列,给你一段序列,求其最长上升子序列的长度,n^2的dp朴素算法过不了,这里用的是nlogn的算法,用了二分查找. O(nlogn)算法 #i ...

  7. Ubuntu 初始配置

      1)在修改source.list前,最好先备份一份 sudo cp /etc/apt/sources.list /etc/apt/sources.list_backu2. 2)执行命令打开sour ...

  8. Linux使用tcpdump命令抓包并使用wireshark分析

    Linux使用tcpdump命令抓包并使用wireshark分析 介绍 有时分析客户端和服务器网络交互的问题时,为了查找问题,需要分别在客户端和服务器上抓包,我们的客户端一般是windows上的,抓包 ...

  9. P1025 数的划分

    P1025 数的划分f[i][j]表示把数i分成j份的方案数,分成两种情况,第一种是最小值是1,另一种是最小值不是1,对于不是1的情况,先都放一个1,那么f[i][j]=f[i-1][j-1]+f[i ...

  10. Java中的访问权限控制

    Java提供了public, private, protected 三个访问权限修饰词,提供了以下四种访问权限控制机制: 1.包访问权限: 2.Public访问权限: 3.Private访问权限: 4 ...