「LG3642-烟花表演」题解

P3642 [APIO2016] 烟花表演

sol

首先肯定考虑树形 DP。

\(f_{i,x}\) 表示以 \(i\) 为根子树中所有叶子到 \(i\) 距离 \(x\) 的最小代价,不难发现这是一个凹的一次连续分段函数。

\(len\) 为点 \(i\) 与父节点的边长,\(f_{i,x}\) 斜率为 \(0\) 区间是 \(\left[l,r\right]\),考虑点 \(i\) 对父节点的贡献函数 \(g_i\)

\[g_{i,x}=\begin{cases} f_{i,x}+len&(x<l)\\ f_{i,l}+(len-(x-l))&(l\le x<l+len)\\ f_{i,l}&(l+len\le x \le r+len)\\ f_{i,l}+((x-r)-len)&(r+len<x) \end{cases} \]

  • 第一种,此时 \(f\) 函数斜率至少为 \(-1\),减少 \(f\)\(x\) 一次至少会带来 \(1\) 的代价,因此直接把 \(len\) 销了必然不劣。
  • 第二种,此时 \(x\) 位于 \(f\) 的斜率为 \(0\) 区间中,尽可能使得其小直到 \(l\) 以最小化操作 \(len\) 的次数,不把 \(x\) 削到 \(l\) 左侧的原因同第一种情况。
  • 第三种,原 \(x\) 就位于斜率为 \(0\) 区间,不用削。
  • 第四种,此时 \(f\) 函数斜率至少为 \(1\),因此右移原 \(x\) 一次至少会带来 \(1\) 的代价,因此直接把 \(len\) 销了必然不劣。

考虑维护这个东西,Slope Trick。

考虑如何把 \(g\) 合并到父节点的 \(f\),使用可并堆直接合并即可。

考虑如何得到 \(g\),考虑 \(g\) 相对 \(f\) 发生的变化。

  • 第一种,上移 \(len\)
  • 第二种,这是一段斜率为 \(-1\) 的线段。
  • 第三种,就是原斜率为 \(0\) 区间右移 \(len\)
  • 第四种,新斜率为 \(0\) 区间右侧直线斜率为 \(1\)

考虑在 Slope Trick 上实现。

  • 第一种,不好单独操作,但在第二种实现时顺带实现了。
  • 第二种,不好单独操作,但在第三种实现时顺带实现了。
  • 第三种,简单,弹出原 \(l,r\) 然后插入 \(l+len,r+len\) 即可。此时原 \(\left[l,l+len\right]\) 区间斜率显然为 \(-1\),同时由于斜率为 \(-1\),因此其左侧函数全部上移了 \(len\) 长度。
  • 第四种,简单,把 \(r\) 右边的全部弹出即可,先做第四种再做第三种会方便很多。

考虑统计答案,也就是根节点的 \(f\) 斜率为 \(0\) 区间的值。不难发现我们知道 \(f_{1,0}\) 也就是所有边权和,直接推到 \(l\) 即可。

每个折点都会使斜率变化 \(1\),因此减去每个折点的横坐标即可。

没了。

code

const int N=3e5+5;

int n,m;
vec<pil> g[N];
int son[N],fa[N];

int cnt;
int rt[N];
int ls[N<<1],rs[N<<1];
int dis[N<<1];ll val[N<<1];
int newnode(ll v){return val[++cnt]=v,cnt;}
int merge(int a,int b){
    if(!a||!b)return a+b;
    if(val[a]<val[b])swap(a,b);
    rs[a]=merge(rs[a],b);
    if(dis[rs[a]]>dis[ls[a]])swap(ls[a],rs[a]);
    dis[a]=dis[rs[a]]+1;
    return a;
}
void pop(int &rt){rt=merge(ls[rt],rs[rt]);}

void dfs(int now){
    for(auto e:g[now]){
        int nxt=e.fir;ll edg=e.sec;
        dfs(nxt);
        ll r=val[rt[nxt]];pop(rt[nxt]);
        ll l=val[rt[nxt]];pop(rt[nxt]);
        rt[nxt]=merge(rt[nxt],merge(newnode(l+edg),newnode(r+edg)));
        rt[now]=merge(rt[now],rt[nxt]);
    }
    while(son[now]-->1)pop(rt[now]);
}
inline void Main(){
    read(n,m);
    ll ans=0;
    rep(i,2,n+m){
        int u,v;read(u,v);
        ++son[u];
        g[u].pub({i,v});
        ans+=v;
    }
    dfs(1);
    pop(rt[1]);
    while(rt[1])ans-=val[rt[1]],pop(rt[1]);
    put(ans);
}
posted @ 2025-07-10 07:56  LastKismet  阅读(12)  评论(0)    收藏  举报