• [NOI2014]购票


    不错的一道码农题。

    首先是个dp很显然了。

    然后展开发现是个斜率优化不错。

    但是取值有范围的。。。。

    直接单调队列并不可以,因为凸包这个东西不是独立贡献答案的。

    方法比较多,,

     

    法一:

    直观的想法是,既然可以转移的是一个区间,那么我们能不能快速得到这个区间的凸包呢?

    线段树维护凸包应运而生。

    只要在所有拆出来的区间中,分别在其维护的凸包上二分斜率即可。所有的值取min

    但是如果dfs顺序来做的话,必须要支持凸包的撤销操作,,,,但是凸包的复杂度是均摊O(1)的,,,可以被特殊构造卡到O(n)。。。。

    所以考虑能不能避免撤销操作。

    还有没有什么可以动态处理树链的方法呢?

    似乎只有树链剖分了。这样用dfn序维护整颗树即可。

     

    修改:

    如果处理好一个点的答案,

    dfs询问处理的时候,也按照dfn序处理,这样就是不断在线段树最后加入一个点了。

    凸包怎么办?可以暴力修改线段树一个链上的凸包,但是要对dis二分,而且常数极大。。

    考虑到,每次询问都是一个之前dfn的区间,所以,当处理了1~3的dfn序之后,代表1~4的区间一定不用来询问。

    所以,当一个区间的最后一个点加入之后,暴力把两个儿子合并即可。这样总线段树凸包的总复杂度O(nlogn)

    询问:

    树链剖分跳重链,把重链上有关的区间内的凸包上二分一下。注意lv[x]的边界处理。我是在树链剖分的时候额外二分了一下(其实线段树再记录min,max也可以)

    O(nlog^3n)其中,树剖的logn不满,二分的logn更虚,所以实际跑的不错。

    #include<bits/stdc++.h>
    #define il inline
    #define reg register int
    #define numb (ch^'0')
    #define int long long
    #define mid ((l+r)>>1)
    #define ls (x<<1)
    #define rs (x<<1|1)
    #define fi first
    #define se second
    using namespace std;
    typedef long long ll;
    il void rd(ll &x){
        char ch;bool fl=false;
        while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
        for(x=numb;isdigit(ch=getchar());x=x*10+numb);
        (fl==true)&&(x=-x);
    }
    namespace Miracle{
    const int N=2e5+5;
    const ll inf=0x3f3f3f3f3f3f3f3f;
    int n,m;
    int dfn[N],df,fdfn[N],sz[N];
    int dep[N];
    int fa[N];
    ll dis[N];
    struct edge{
        int nxt,to;
        ll val;
    }e[2*N];
    int hd[N],cnt;
    ll pv[N],qv[N],lv[N];
    int top[N],son[N];
    void add(int x,int y,ll z){
        e[++cnt].nxt=hd[x];
        e[cnt].to=y;
        e[cnt].val=z;
        hd[x]=cnt;
    }
    ll ans[N];
    void dfs1(int x,int d){
        sz[x]=1;
        dep[x]=d;
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            dis[y]=dis[x]+e[i].val;
            dfs1(y,d+1);
            sz[x]+=sz[y];
            if(sz[y]>sz[son[x]]) son[x]=y;
        }
    }
    void dfs2(int x){
        dfn[x]=++df;fdfn[df]=x;
        if(!top[x]) top[x]=x;
        if(son[x]) top[son[x]]=top[x],dfs2(son[x]);
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(y==son[x]) continue;
            dfs2(y);
        }
    }
    struct node{
        vector<pair<ll,ll> >tu;
        ll mi;//mindis 
    }t[4*N];
    int b[N],tot;
    double cross(ll x1,ll y1,ll x2,ll y2){
        return (double)x1*y2-(double)x2*y1;
    }
    void pop(int x,ll d,ll f){
        while(t[x].tu.size()>1){
            int size=t[x].tu.size();
        //    cout<<" size "<<endl;
            double tmp=cross(t[x].tu[size-1].fi-d,t[x].tu[size-1].se-f,t[x].tu[size-2].fi-d,t[x].tu[size-2].se-f);
            if(tmp>=0) t[x].tu.pop_back();
            else break;
        }
        t[x].tu.push_back(make_pair(d,f));
    }
    void pushup(int x){
    //    cout<<" pushing "<<x<<endl;
        t[x].mi=min(t[ls].mi,t[rs].mi);
        int le=0,ri=0;
        for(int i=1;i<=(int)(t[x<<1].tu.size())+(int)(t[x<<1|1].tu.size());++i){
        //    cout<<" ii "<<i<<" ---------------- "<<endl;
            if(le==(int)t[x<<1].tu.size()){
            //    cout<<" here "<<endl;
                pop(x,t[rs].tu[ri].fi,t[rs].tu[ri].se);++ri;                                                                                                                                      
            }else if(ri==(int)t[x<<1|1].tu.size()){
                pop(x,t[ls].tu[le].fi,t[ls].tu[le].se);++le;
            }
            else if(t[ls].tu[le].fi==t[rs].tu[ri].fi){//warning!!!
                if(t[ls].tu[le].se<t[rs].tu[ri].se) pop(x,t[ls].tu[le].fi,t[ls].tu[le].se);//,++le;
                else pop(x,t[rs].tu[ri].fi,t[rs].tu[ri].se);//,++ri;
                ++le;++ri;++i;
            }
            else if(t[ls].tu[le].fi<t[rs].tu[ri].fi){
                pop(x,t[ls].tu[le].fi,t[ls].tu[le].se);++le;    
            }else{
                pop(x,t[rs].tu[ri].fi,t[rs].tu[ri].se);++ri;
            }
        }
    }
    void upda(int x,int l,int r,int p,ll d,ll f){
    //    cout<<" updaing "<<p<<" "<<d<<" "<<f<<endl;
        if(l==r){
            t[x].mi=d;
            t[x].tu.push_back(make_pair(d,f));
            return;
        }
        if(p<=mid) upda(x<<1,l,mid,p,d,f);
        else upda(x<<1|1,mid+1,r,p,d,f);
        if(p==r) pushup(x);
    }
    double slope(ll x1,ll y1,ll x2,ll y2){
        if(x2==x1){
            if(y1>y2) return inf;
            return -inf;
        }
        return ((double)y1-(double)y2)/((double)x1-(double)x2);
    }
    ll query(int x,int l,int r,int L,int R,ll k){//dis>=c        // return f[j]-dis[j]*k
        if(L<=l&&r<=R){
            ll ret=0;
    //        if(t[x].mi>=c){
    //            
    //        }else{
    //            ret=query(x<<1,l,mid,L,R,c,k);
    //            ret=min(ret,query(x<<1|1,))
    //        }
            int le=0,ri=t[x].tu.size()-1;
            while(le<=ri){
                int md=(le+ri)>>1;
                if(md==0){
                    if(le==ri) ret=0;
                    else{
                        ll t0=t[x].tu[0].se-t[x].tu[0].fi*k;
                        ll t1=t[x].tu[1].se-t[x].tu[1].fi*k;
                        if(t0<t1) ret=0;
                        else ret=1;
                    }
                    break;
                }
                else{
                    double sl=slope(t[x].tu[md].fi,t[x].tu[md].se,t[x].tu[md-1].fi,t[x].tu[md-1].se);
                    if(sl<=(double)k) ret=md,le=md+1;
                    else ri=md-1; 
                }
            }
        //    cout<<" sz "<<t[x].tu.size()<<endl;
        //    cout<<" ret "<<ret<<endl;
            return t[x].tu[ret].se-t[x].tu[ret].fi*k;
        }
        ll ret=inf;
        if(L<=mid) ret=min(ret,query(x<<1,l,mid,L,R,k));
        if(mid<R) ret=min(ret,query(x<<1|1,mid+1,r,L,R,k));
        return ret;
    }
    void wrk(int x){
        //cout<<" wrking "<<x<<endl;
        int y=fa[x];
        ans[x]=inf;
        while(1){
            //cout<<" yy "<<y<<endl;
            if(dis[x]-dis[y]>lv[x]) break;
            if(!y) break;
            if(dis[x]-dis[top[y]]<=lv[x]){
                //cout<<" goto top "<<endl;
                ans[x]=min(ans[x],query(1,1,n,dfn[top[y]],dfn[y],pv[x]));
            }
            else{
                int l=dfn[top[y]],r=dfn[y];
                int up=y;
                while(l<=r){
                    if(dis[x]-dis[fdfn[mid]]<=lv[x]) up=fdfn[mid],r=mid-1;
                    else l=mid+1;
                }
                ///cout<<" up "<<up<<endl<<" pv "<<pv[x]<<endl;
                ans[x]=min(ans[x],query(1,1,n,dfn[up],dfn[y],pv[x]));
            }
            y=fa[top[y]];
        }
        //cout<<" ans[x] "<<ans[x]<<endl;
        ans[x]+=qv[x]+dis[x]*pv[x];
    }
    void dfs3(int x){
    //    cout<<" sloving "<<x<<endl;
        if(x==1){
            upda(1,1,n,1,0,0);
        }    
        else{
            wrk(x);
        //    cout<<" after wrk "<<ans[x]<<endl;
            upda(1,1,n,dfn[x],dis[x],ans[x]);
        //    cout<<" upda finished "<<endl;
        }
        if(son[x]) dfs3(son[x]);
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            if(y==son[x]) continue;
            dfs3(y);
        }
    }
    int main(){
        rd(n);rd(m);
        int s;
        for(reg i=2;i<=n;++i){
            rd(fa[i]);rd(s);rd(pv[i]);rd(qv[i]);rd(lv[i]);
            add(fa[i],i,s);
        }
        dis[0]=0;
        dfs1(1,1);
        dfs2(1);
    //    for(reg i=1;i<=n;++i){
    //        cout<<i<<" : "<<dfn[i]<<" "<<fa[i]<<" "<<top[i]<<endl;
    //    }
        dfs3(1);
        for(reg i=2;i<=n;++i){
            printf("%lld
    ",ans[i]);
        }
        return 0;
    }
    
    }
    signed main(){
        Miracle::main();
        return 0;
    }
    
    /*
       Author: *Miracle*
       Date: 2018/12/20 8:00:53
    */
    View Code

     

     

    法二:

    这个跳树链实在还是多一个logn比较吃亏

    如果可以dfs顺序来做就好了。这样直接一条链下来,凸包上直接二分就好。

    上面已经分析,这样不能撤销。

    树上求链还有什么算法呢?

    点分治!

    我们找到当前的重心H,先把H和根相连的部分和根的其他子树递归下去。

    ,,把和根相连的部分的这条链找出来,建一个凸包。

    H的所有儿子的值也许都可以在这个凸包上找到决策点,dfs这些儿子的子树然后在凸包上二分

    递归H的子树部分联通块。

    这样,按顺序地,我们可以得到某个点的所有可能决策点的转移。

    (其实思想有点类似CDQ分治,考虑H到根的路径对后面分治树部分的影响。)

    理论O(nlog^2n) 二分的logn同样比较虚。

     

     

    法三:

    dfs真的不能直接进行吗?

    其实可以。

    一个厉害的东西是二进制分组。

    至于撤销,

    类似分块的处理,

    如果多了4个1,那么把前两个1合成2,

    如果多了4个2,那么把前两个2合成4

    。。。

    删除暴力干掉这个凸包

    这样,每添加2^k个点,才会建造一个2^k的凸包。

    每删除2^k个点,才会删除一个2^k的凸包。

    所以,凸包的建造复杂度,基本和O(nlogn)同级。

    就是往前面的暴力找常数比较大。

    复杂度O(nlog^2n)+大常数。

     

  • 相关阅读:
    日期格式
    v-model修饰符
    从0到1构建全栈知识体系
    Element组件,v-for循环,表单验证方法
    上传文件转换为base64图片
    gulp4进阶
    gulp4快速入门
    TS-接口
    webpack4.0高级
    webpack4.0基础
  • 原文地址:https://www.cnblogs.com/Miracevin/p/10149101.html
Copyright © 2020-2023  润新知