[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\n",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)+大常数。

 

posted @ 2018-12-20 14:24  *Miracle*  阅读(374)  评论(0编辑  收藏  举报