做题笔记:洛谷P8966

考场没做出来。

赛后看了讲解PPT,补了,是一道很妙的题,所以来写一篇笔记。

尝试过交题解,但不给过,又见题解区已经有一篇非常好的非官方题解,个人也感觉写的太草也太抽象,达不到题解要求,就不占TA风头了,这仅仅是一篇笔记。

步入正题了

此题首先要注意一点:

不开long long见祖宗!(连挂两次)

正解:

首先考虑最优策略:

当一个点的两个子树未被装满时,两棵树的电荷显然差\(1\)或相等。

然后考虑神明的最优策略:显然要让球总是尽可能远离目标点。

所以两棵子树电荷相等时相等时神明总是要让球向远离目标点的方向下落。

所以只有当两棵子树电荷差 \(1\) 时,凡人可以让球相对靠近目标点。

那怎么做呢?

不妨考虑两次投球时球的电荷,倘若两棵子树未满且两棵子树电荷值相等。

当球的电荷不同时显然会同时落在一棵子树;

当球的电荷相同时则两个球则会落在不同子树,也就必然会有一个球相对靠近目标点,而因为相等时神明总是要往远离目标点的方向投球,所以靠近目标点的球显然可以是当两棵子树电荷差 \(1\) 时投下的那个球。

所以凡人的一种最优策略显然是一直投正电或负电。

这样必然能保证两棵子树电荷差 \(1\) 时让球相对靠近目标点。

这种策略确实一时很难理解,如果想更好地理解这种思路,可以将凡人和神明的最优策略带入样例模拟,这样很快就能明白。

一直投同一种电荷,显然让问题变成了模拟。

首先,球要填满目标点的子树。

然后发现:

设当前需要 \(ans\) 个球坠向目标点, \(c\) 表示旁边子树的大小,

则旁边的子树会落下 \(min(ans,c)\) 个球,新的 \(ans\) 显然是 \(ans+min(ans,c)\)

所以考虑不断从目标节点向上跳直到到了根节点不能再跳,每次更新 \(ans\)

最后就输出答案。

复杂度撑死是 \(O(n^2)\) ,也就是一条链,满足题意。

//writer:Oier_szc
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
int n;
int fa[N],ch[N][3],c[N],sz[N];
void get_size(int u)
{
	sz[u]=c[u];
	for(int i=1;i<=ch[u][0];++i)
	{
		int to=ch[u][i];
		get_size(to);
		sz[u]+=sz[to];
	}
}
signed main()
{
	scanf("%lld",&n);
	for(int i=2;i<=n;++i)
	{
		scanf("%lld",&fa[i]);
		ch[fa[i]][++ch[fa[i]][0]]=i;
	}
	for(int i=1;i<=n;++i) scanf("%lld",&c[i]);
	get_size(1);
	for(int i=1;i<=n;++i)
	{
		int now=i,ans=sz[i];
		while(fa[now])
		{
			int this_ch=now,other_ch=-1;
			now=fa[now];
			for(int j=1;j<=ch[now][0];++j)
			{
				if(ch[now][j]!=this_ch) other_ch=ch[now][j];
			}
			if(other_ch==-1) continue;
			//other_ch==兄弟节点 
			ans+=min(sz[other_ch],ans);
		}
		printf("%lld ",ans);
	}
	return 0;
}
posted @ 2023-02-02 10:46  Oier_szc  阅读(83)  评论(0)    收藏  举报