qoj #18328. 假想对冲 题解
首先对于一个子树 \(i\) ,非常明显的,它每一层的子节点的权值和都要 \(<=w_i\) ,肯定可以长剖dp但我不会做截断。不过考虑这个对 \(w_i\) 做截断,本质是一个区间推平,然后发现随着子节点层级的加深,权值和肯定是不增的,那截断就成了一个前缀推平。这样就可以上链表维护了,直接用链表记录一个权值和 \(val\) 和权值等于它的层数 \(len\) ,相当于一个分段常数函数,然后每次更新父节点都要前缀推平成一个节点,往 \(fa\) 上 \(merge\) 就完事了,注意到树上的每个点只会新增一个链表节点,每个节点也只会被删一次或合并一次,均摊下来复杂度是 \(O(n)\) 的。
代码:
#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e6+6;
int n;
int fa[N];
ll w[N];
struct B{
ll val;
int len;
int nxt;
}a[N];
ll ans[N];
int hd[N];
inline int merge(int x,int y){
if(!x||!y) return x|y;
int head=0,tail=0;
while(x&&y){
if(a[x].len>a[y].len) swap(x,y);
a[y].len-=a[x].len;
a[x].val+=a[y].val;
if(!head) head=tail=x;
else a[tail].nxt=x,tail=x;
if(!a[y].len) y=a[y].nxt;
x=a[x].nxt;
}
a[tail].nxt=x|y;
return head;
}
inline void solve(){
cin>>n;
for(int i=2;i<=n;i++) cin>>fa[i];
for(int i=2;i<=n;i++) cin>>w[i];
for(int i=1;i<=n;i++) ans[i]=hd[i]=0;
for(int i=n;i>=2;i--){
ll tmp=ans[i];
int new_len=0;
int h=hd[i];
while(h&&a[h].val>=w[i]){
tmp-=a[h].val*a[h].len;
new_len+=a[h].len;
h=a[h].nxt;
}
a[i]={w[i],new_len+1,h};
tmp+=(new_len+1)*w[i];
hd[fa[i]]=merge(hd[fa[i]],i);
ans[fa[i]]+=tmp;
}
for(int i=1;i<=n;i++) cout<<ans[i]<<' ';
cout<<'\n';
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号