loj#3052. 「十二省联考 2019」春节十二响

题意 : link

我一开始考虑的贪心是从大往小选,然后需要跳着,没有任何规律,很显然没有前途 .

既然从大往小选不行,那么就考虑树的一个常见思路,从叶子往根考虑,此时,思路就豁然开朗了,对于以 \(x\) 为跟的子树来说,\(x\) 是必须要单独成段的,那么,考虑依次加入儿子,对于儿子 \(a\)\(b\)\(b\) 的节点可以加入 \(a\) ,但是每个 \(a\) 中的节点只能被加入一次,这种操作,让人想到了 启发式合并 .

但是,如何插入呢?发现,对于两个儿子 \(a\)\(b\) 来说,对于 \(b\) 来说,能插入就插入,是尽可能好的,其次,发现,将 \(b\) 从大往小依次插入 \(a\) 的从大往小,这是最好的 . 能保证最大值的和最小 .

这样的合并可以用 \(\rm{set}\) 维护,但是,出现了一个问题,时限只有 \(1s\) ,时间复杂度是不是 \(O(n\log^2 n)\) 的呢?

其实不是,因为我们合并了,但是我们没有真的插入,\(sz(x)\) 的大小是儿子节点中 \(sz\) 最大的节点的大小 \(+1\) . 也就是相当于每个节点初始插入到 \(\rm{set}\) 中是一个 \(\log\) , 只会和其他节点合并,并且删除一次,是一个 \(\log n\) , 所以总的操作次数是线性的 . 时间复杂度即为 \(O(n\log n)\).

时间复杂度 : \(O(n\log n)\)

空间复杂度 : \(O(n)\)

code

#include<bits/stdc++.h>
using namespace std;
char in[100005];
int iiter=0,llen=0;
inline char get(){
	if(iiter==llen)llen=fread(in,1,100000,stdin),iiter=0;
	if(llen==0)return EOF;
	return in[iiter++];
}
inline int rd(){
	char ch=get();while(ch<'0'||ch>'9')ch=get();
	long long res=0;while(ch>='0'&&ch<='9')res=(res<<3)+(res<<1)+ch-'0',ch=get();
	return res;
}
inline void pr(long long res){
	if(res==0){putchar('0');return;}
	static int out[20];int len=0;
	while(res)out[len++]=res%10,res/=10;
	for(int i=len-1;i>=0;i--)putchar(out[i]+'0');
}
#define pb push_back
const int N=2e5+10;
int n;
int a[N];
vector<int>g[N];
multiset<int>s[N];
void dfs(int x,int fa){
	for(int to:g[x]){
		if(to==fa)continue;
		dfs(to,x);
		if((int)s[to].size()>(int)s[x].size())s[x].swap(s[to]);
		vector<int>vd,vi;
		for(auto i1=s[x].begin(),i2=s[to].begin();i2!=s[to].end();i1++,i2++){
			vd.pb(*i1),vi.pb(min(*i1,*i2));
		}
		for(auto val:vd)s[x].erase(s[x].find(val));
		for(auto val:vi)s[x].insert(val);
		s[to].clear();
	}
	s[x].insert(-a[x]);
}
int main(){
	n=rd();
	for(int i=0;i<n;i++)a[i]=rd();
	for(int i=1;i<n;i++)g[rd()-1].pb(i);
	dfs(0,-1);
	long long ans=0;
	for(auto val:s[0])ans-=val;
	pr(ans);
	return 0;
}
/*inline? ll or int? size? min max?*/

posted @ 2022-02-02 23:22  xyangh  阅读(13)  评论(0)    收藏  举报