Luogu4694「PA2013」Raper / CF802O April Fools' Problem (hard)

线段树维护模拟费用流!

这篇题解旨在给 \(\rm{Clarisls}\) 的线段树写一个详细点的解释,原博客参考https://www.cnblogs.com/clrs97/p/5124895.html

思考如果将本题数据范围缩小,放到一个费用流能过的复杂度

一种可行的建图就是建立一虚拟源点限制流量,同时建一排虚点 \(p_1,p_2\dots p_n\)

虚源点向虚点连流量 \(1\) 费用 \(a_i\) 的边,虚点向汇点连 \((1,b_i)\) 的边,源点向虚源点连 \((0,k)\) 的边,虚点直接从前往后连 \((+\infty,0)\) 的边

剩下要做的是写一个 \(EK\)

既然可以使用费用流做,那么必然满足函数是下凸的,即每次选出来的点对的权值和单调 不降

同时一个比较经典的结论是选择 \(k\) 个点对所得到的点集是选择 \(k+1\) 对点所得到的点集的子集,但是匹配不一定相同,正确性是显然的

(然后把网络流建图扔掉)

考虑贪心选出最小的合法点对,那么问题本质上变成了最小的带权括号匹配,转化后维护序列所选择的左右括号前缀和,最后得到的序列需要满足任意前缀和非负

按照 \(\rm{Clarisls}\) 的博客所写的定义:

  • \(mina/minb\) 表示区间里面 \(a/b\) 数组最小值所在的位置

  • \(va\) 表示区间里面选择 \(\rm{pos_a\le pos_b}\) 的最小合法方案

  • \(vc\) 表示区间里面选择 \(\rm{pos_a>pos_b}\) 的最小合法方案

  • \(vb\) 表示区间里面选择 \(\rm{pos_a> pos_b}\) 的最小合法方案,同时满足所选择的两点之间的前缀和的最小值大于区间里面前缀和的最小值

  • \(tag/mn\) 表示区间加法懒标记和区间前缀和最小值

  • \(\rm{alim}\) 表示满足 \([l,alim-1]\) 区间中前缀和最小值大于整个区间前缀和最小值的 \(A\) 中值最小的一个

  • \(\rm{blim}\) 表示满足 \([blim,r]\) 区间中前缀和最小值大于整个区间前缀和最小值的 \(B\) 中值最小的一个

注意:\(\rm{alim}\) 定义中区间是不到其本身位置的,但是 $\rm{blim} $ 中到了,所以最初建线段树时可以给 \(\rm{alim}\) 赋值,但后者不能

\(\rm{push\_up}\) 的过程包括:

  • 使用子区间信息直接更新:\(\rm{mina,minb,mn,va,vb,vc}\)

  • 使用 left->mina,right->minb 更新 \(va\)

  • 使用 left->minb,right->mina 更新 \(vc\)

以上根据含义,正确性显然,下述转移正确性耐人寻味,建议思考

  • 对于 left->mn>right->mn 的情况

    因为左边的最小值大,所以 left->vc 可以更新 \(vb\)

    左边的 \(\rm{minb/mina}\) 被解放,可以先后更新 \(\rm{vb/alim}\)

    注意因为是区间交叉时的信息更新,所以左边一定选 \(\rm{B}\)

  • 对于 left->mn<right->mn 的情况

    右边的最小值大,那么 right->vc 可以更新 \(vb\)

    右边的 \(\rm{mina/minb}\) 被解放,可以先后更新 \(\rm{vb/blim}\)

    仍然注意因为是区间交叉时的信息更新,右子区间选 \(\rm{A}\)

  • 对于 left->mn=right->mn 的情况

    因为最小值在两边都能取到,只能用 left->minb,right->mina 更新 \(vb/alim/blim\)

最后,代码是最好的教程,可以配合食用

inline void push_up(int p){
    #define ls p<<1 
    #define rs p<<1|1
    va[p]=min(min(va[p<<1],va[p<<1|1]),node(mna[p<<1],mnb[p<<1|1])); 
    vc[p]=min(min(vc[p<<1],vc[p<<1|1]),node(mna[p<<1|1],mnb[p<<1]));
    vb[p]=min(vb[p<<1],vb[p<<1|1]);
    if(a[mna[p<<1]]<a[mna[p<<1|1]]) mna[p]=mna[p<<1]; 
    else mna[p]=mna[p<<1|1];
    if(b[mnb[p<<1]]<b[mnb[p<<1|1]]) mnb[p]=mnb[p<<1]; 
    else mnb[p]=mnb[p<<1|1];
    if(mn[ls]>mn[rs]){
        vb[p]=min(vb[p],node(alim[rs],mnb[ls]));
        vb[p]=min(vb[p],vc[ls]);
        if(a[mna[p<<1]]>a[alim[p<<1|1]]) alim[p]=alim[p<<1|1]; else alim[p]=mna[p<<1];
        blim[p]=blim[p<<1|1];
    }else if(mn[ls]<mn[rs]){
        vb[p]=min(vb[p],node(mna[rs],blim[ls]));
        vb[p]=min(vb[p],vc[rs]);
        if(b[blim[p<<1]]>b[mnb[p<<1|1]]) blim[p]=mnb[rs]; else blim[p]=blim[ls];
        alim[p]=alim[p<<1];
    }else{
        vb[p]=min(vb[p],node(alim[rs],blim[ls]));
        alim[p]=alim[ls]; blim[p]=blim[rs];
    } mn[p]=min(mn[p<<1],mn[p<<1|1]); return ;
    #undef ls
    #undef rs
}

实现的时候:

  • 变量维护大多是位置,所以取 \(\min\) 要注意

  • 可以直接设 \(A[0]=B[0]=+\infty\) ,会方便一些

诶你这不是线段树加速贪心吗?咋跟模拟费用流扯上关系了?

对于选择的括号形如 \(()\) 时,是在虚点一排流经了 \([l,r]\) 的点

而如果在这之间选择了 \()(\) 的括号就是将 \([pl,pr]\) 流了反向边,现在的流经情况是 \(S\to l\to pl\to T\)\(S\to pr\to r\to T\)

也就是把 \([pl,pr]\) 之间的流量逼了回去

posted @ 2021-07-31 17:29  没学完四大礼包不改名  阅读(149)  评论(0)    收藏  举报