分草莓(树上切割)
自述:判断放进了循环,debug了1.5个小时,最后还是请sxk大佬才改出,感谢sxk大佬
题目描述
院子里有一颗又高又大的草莓树,草莓树有n个节点,每个节点都结了一个草莓,吃掉第i个结点的草莓可以得到ai的营养值,由于草莓可能会坏掉,所以ai可能为负值,也可能为0。
现在要砍掉这颗树的两条边,使树变成三份,并且使得三份各自草莓营养值的和恰好一样。请问是否有这样的方法呢?如果有,请输出YES,否则输出NO。
输入输出
第一行一个数t,表示测试点的个数。
接下来t组:
每组第一行一个数n,表示结点的个数。
接下来n行,每行两个数fai和ai,表示第i个结点的父亲是fai,第i个结点有营养值为ai的草莓,根节点的fai记为0.
【输出格式】
输出t行,如果第t组有解,则输出YES,否则输出NO
样例
2
6
2 4
0 5
4 2
2 1
1 1
4 2
6
2 4
0 6
4 2
2 1
1 1
题目分析
这是一道可以体现Dp思想的爆搜题,(就是用不到Dp的Dp题)
对于本题,我们考虑,既然要分成 3 份,那么对于所有营养值之和不为 3 的倍数的数据,输出“NO”。
这棵树满足一个神奇的性质:只要有子树大小等于营养值之和/3,那么这棵子树可以删去,且对后面判断剩下子树时无后效性(Dp思想)。
在这里给出证明:
在这里定义 子树大小等于营养值之和/3 为合法子树。
对于任意一棵合法的树,根节点所连的边下可能直接有一棵合法子树,也可能是包含了另一颗合法子树的合法子树(删去包含的合法子树后)。那么在删去一颗合法子树后,剩下的一定可以继续分出合法子树。否则全树不合法。
对于存在大于3个合法子树的树,两最近合法子树节点之间的营养值之和为 0 ,否则这两个合法子树不存在。而这营养值之和为零的一段节点,可以放于相邻的任意一颗子树。所以必定可以分出 3 棵合法子树 。
如不满足上述条件,显然无法分割。
证毕。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005 ;
inline int read()
{
int s = 0,w = 1;
char g = getchar();
while(g<'0'||g>'9'){if(g=='-')w*=-1;g = getchar();}
while(g>='0'&&g<='9'){s = s*10+g-'0';g = getchar();}
return s*w;
}
int N , T , S , size[ MAXN ] , sum[ MAXN ] , ccc ;
int tot , nex[ MAXN ] , to[ MAXN ] , head[ MAXN ] ;
void add( int x , int y ){
tot++ ;
to[ tot ] = y ;
nex[ tot ] = head[ x ] ;
head[ x ] = tot ;
}
void prepare_(){
N = read() ;
for( register int i = 1 ; i <= N ; i ++ ){
int m1 = read() ;
size[ i ] = read() ;
add( m1 , i ) ; S+=size[ i ] ;
}
for( register int i = 1 ; i <= N ; i ++ )sum[ i ] = size[ i ] ;
}
void dfs( int u ){
for( register int i = head[ u ] ; i ; i = nex[ i ] ){
dfs( to[ i ] ) ;
sum[ u ] += sum[ to[ i ] ] ;
}
if( sum[ u ] == S/3 )sum[ u ] = 0 , ccc++ ;
}
int Ans_check(){
if( ccc >= 3 )printf("YES
");
else printf("NO
");
}
void clear(){
memset( head , 0 , sizeof(head) );
S = tot = ccc = 0 ;
}
void init_(){
freopen("strawberry.in","r",stdin);
freopen("strawberry.out","w",stdout);
T = read() ;
}
int main()
{
init_();
while( T-- ){
prepare_();
if( S%3 ){ printf("NO
") ; clear() ; continue ; }
dfs( 0 ) ;
Ans_check();
clear();
}
return 0 ;
}