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?*/

浙公网安备 33010602011771号