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;
}
posted @ 2026-10-10 12:50  bjq521  阅读(2)  评论(0)    收藏  举报