zoukankan      html  css  js  c++  java
  • bzoj千题计划251:bzoj3672: [Noi2014]购票

    http://www.lydsy.com/JudgeOnline/problem.php?id=3672

    法一:线段树维护可持久化单调队列维护凸包 斜率优化DP

    设dp[i] 表示i号点到根节点的最少花费

    dis[i] 表示 点i到根节点的距离

    dp[i]= min { (dis[i]-dis[j])* P[i] + Q[i] + dp[j] }   j是i的祖先且dis[i]-dis[j]<=L[i]

    即 dp[i]+dis[j]*P[i]=dp[j]+dis[i]*P[i]+Q[i]

    斜率优化,dp[i]=斜率为P[i] 的直线 截(dis[j],dp[j])所得的截距最小值

    如果不考虑L[i] 的限制:

    那就可以用一个可持久化的单调队列维护 根节点到当前点的路径上所有的点构成的下凸壳

     

    考虑如何去除兄弟节点的子树对单调队列的影响

     

    即在一个节点退出dfs时,将单调队列恢复为这个节点开始dfs的情况

    头指针:头指针的前移不会涉及单调队列元素的修改,因为从根往下走不能保证斜率单调,所以头指针不能出队,需要二分找到最小的 斜率>P[i] 的点来更新dp[i]

    尾指针:尾指针的前移会涉及到单调队列元素的替换,但替换只会替换一个元素,所以记录下 替换的是谁,原来的尾指针是谁,退出的时候恢复即可

    加上L[i]的限制:

    用线段树维护,若线段树节点的区间为[l,r],那这个节点维护的是当前链上  深度为[l,r]的点构成的凸包

    即由原来的维护一个单调队列变成维护nlogn个单调队列

    每个节点开一个vector? 光开nlogn个vector就TLE了吧

    开一个nlogn的数组v存储整条链上凸包内的点

    在建树的时候,按节点的顺序给头指针分一个位置x,若节点的大小为y,

    那么这个节点维护的单调队列 中的点会出现在v数组的[x,x+y-1]位置

    这样线段树内只需要存储单调队列的头尾指针即可

     

    单调队列采用 左闭右开 的好处:

    多个可持久化单调队列,头尾指针相连

    如果头尾指针是左闭右闭

    如果队列为空,tail<head,导致 后一个队列的尾指针=前一个队列的头指针

    这样尾指针替换更改,记录的替换位置为tail,即前一个队列的head

    退出dfs替换回去 错误的替换了前一个队列的head

    头尾指针左闭右开就不会有这个问题

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
     
    using namespace std;
     
    #define N 200001
     
    typedef long long LL;
     
    int n;
    int P[N];
    LL Q[N],L[N];
     
    int front[N],to[N],nxt[N],tot;
    LL val[N];
     
    struct node
    {
        int head,tail;
    }tr[N<<2];
    int v[N*19];
     
    int sum;
     
    LL dep[N],dis[N];
    LL dp[N];
     
    int re_t[N][19],re_w[N][19];
     
    template<typename T>
    void read(T &x)
    {
        x=0; char c=getchar();
        while(!isdigit(c)) c=getchar();
        while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
    }
     
    void add(int u,int v,LL w)
    {
        to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w;
    }
     
    void build(int k,int l,int r)
    {
        tr[k].head=sum+1;
        tr[k].tail=tr[k].head;
        sum+=r-l+1;
        if(l==r) return;
        int mid=l+r>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
    }
     
    inline double slope(int i,int j)
    {
        return 1.0*(dp[j]-dp[i])/(dis[j]-dis[i]);
    }
     
    int find_ans(int k,int p)
    {
        int l=tr[k].head;
        int r=tr[k].tail-1;
        if(l==r) return v[l];
        r--;
        int mid,tmp=-1;
        while(l<=r)
        {
            mid=l+r>>1;
            if(p<slope(v[mid],v[mid+1])) tmp=mid,r=mid-1;
            else l=mid+1;
        }
        if(tmp==-1) return v[tr[k].tail-1];
        return v[tmp];
    }   
     
    int query(int k,int l,int r,int opl,int opr,int p)
    {
        if(l>=opl && r<=opr) return find_ans(k,p);
        int mid=l+r>>1;
        if(opr<=mid) return query(k<<1,l,mid,opl,opr,p);
        else if(opl>mid) return query(k<<1|1,mid+1,r,opl,opr,p);
        else
        {
            int t1=query(k<<1,l,mid,opl,opr,p);
            int t2=query(k<<1|1,mid+1,r,opl,opr,p);
            if(p<slope(t1,t2)) return t1;
            return t2;
        }
    }   
     
    int find(int r,LL lim)
    {
        int l=1;
        int tmp,mid;
        while(l<=r)
        {
            mid=l+r>>1;
            if(dep[mid]>=lim) tmp=mid,r=mid-1;
            else l=mid+1;
        }
        return tmp;
    }
     
    int in_queue(int k,int j)
    {
        if(tr[k].head==tr[k].tail) return tr[k].head;
        int l=tr[k].head,r=tr[k].tail-2,tmp=-1,mid;
        while(l<=r)
        {
            mid=l+r>>1;
            if(slope(v[mid],v[mid+1])>slope(v[mid],j)) tmp=mid+1,r=mid-1;
            else l=mid+1;
        }
        if(tmp==-1) return tr[k].tail;
        return tmp;
    }
     
    void insert(int k,int l,int r,int x,int w,int d)
    {
        int pos=in_queue(k,w);
        re_t[w][d]=tr[k].tail;
        re_w[w][d]=v[pos];
        v[pos]=w;
        tr[k].tail=pos+1;
        if(l==r) return;
        int mid=l+r>>1;
        if(x<=mid) insert(k<<1,l,mid,x,w,d+1);
        else insert(k<<1|1,mid+1,r,x,w,d+1);
    }
     
    void del(int k,int l,int r,int x,int w,int d)
    {
        v[tr[k].tail-1]=re_w[w][d];
        tr[k].tail=re_t[w][d];
        if(l==r) return;
        int mid=l+r>>1;
        if(x<=mid) del(k<<1,l,mid,x,w,d+1);
        else del(k<<1|1,mid+1,r,x,w,d+1);
    }
     
    void dfs(int x,int d)
    {
        if(x!=1)
        {
            LL lim=dis[x]-L[x];
            int j=query(1,1,n,find(d-1,lim),d-1,P[x]);  
            dp[x]=(dis[x]-dis[j])*P[x]+Q[x]+dp[j];
        }
        insert(1,1,n,d,x,1);
        for(int i=front[x];i;i=nxt[i])
        {
            dis[to[i]]=dis[x]+val[i];
            dep[d+1]=dep[d]+val[i];
            dfs(to[i],d+1);
        }
        del(1,1,n,d,x,1);
    }
     
    int main()
    {
        int t;
        read(n); read(t);
        int f; LL s;
        for(int i=2;i<=n;++i)
        {
            read(f); read(s); read(P[i]); read(Q[i]); read(L[i]);
            add(f,i,s);
        }
        sum=0; build(1,1,n);
        insert(1,1,n,1,1,1);
        dfs(1,1);
        for(int i=2;i<=n;++i) cout<<dp[i]<<'
    ';
        return 0;
    }
    
    View Code

    法二、点分治+斜率优化dp

    朴素的dp是往上找能更新它的点

    这里往下找它能更新的点

    具体做法:

    找出分治重心后,先 解决重心 的祖先(包括重心)那块儿

    然后求出 原树根节点到重心的凸包 更新重心的子树

    至于 距离的限制,我们把重心的子树中所有点 按能更新它的 深度最小的城市 从大到小排序

    枚举这些点i

    构造重心到原树根节点的凸包时,从重心往上依次加点,只加入 深度>= 当前点i深度限制 的点

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    #define N 200001 
    
    typedef long long LL;
    
    int n;
    int front[N],nxt[N],to[N],tot;
    LL val[N];
    
    int fa[N],P[N];
    LL Q[N],L[N];
    
    LL dis[N];
    
    bool vis[N];
    
    int root;
    int siz[N],mx[N];
    
    struct node
    {
        int id;
        LL lim;
    }e[N];
    int cnt;
    int st[N];
    
    LL dp[N];
    
    template<typename T> 
    void read(T &x)
    {
        x=0; char c=getchar();
        while(!isdigit(c)) c=getchar();
        while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
    }
    
    void add(int u,int v,LL w)
    {
        to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; val[tot]=w;
    }
    
    void dfs(int x)
    {
        for(int i=front[x];i;i=nxt[i])
        {
            dis[to[i]]=dis[x]+val[i];
            dfs(to[i]);
        }
    } 
    
    void get_siz(int x,int all)
    {
        siz[x]=1; mx[x]=0;
        for(int i=front[x];i;i=nxt[i])
            if(!vis[to[i]])
            {
                get_siz(to[i],all);
                siz[x]+=siz[to[i]];
                if(siz[to[i]]>mx[x]) mx[x]=siz[to[i]]; 
            }
        mx[x]=max(mx[x],all-siz[x]);
        if(mx[x]<mx[root] && siz[x]>1) root=x;
    } 
    
    void dfs2(int x)
    {
        e[++cnt].id=x;
        e[cnt].lim=dis[x]-L[x];
        for(int i=front[x];i;i=nxt[i])
            if(!vis[to[i]]) dfs2(to[i]);
    }
    
    bool cmp(node p,node q)
    {
        return p.lim>q.lim;
    }
    
    double slope(int i,int j) 
    {
        return 1.0*(dp[j]-dp[i])/(dis[j]-dis[i]);
    }
    
    void solve(int x,int all)
    {
        if(all==1) return;
        root=0;
        get_siz(x,all);
        int rt=root;
        for(int i=front[root];i;i=nxt[i])
            vis[to[i]]=true;
        solve(x,all-siz[root]+1);
        cnt=0;
        for(int i=front[rt];i;i=nxt[i])
            dfs2(to[i]);
        sort(e+1,e+cnt+1,cmp); 
        int now=rt,top=0;
        for(int i=1;i<=cnt;++i)
        {
            while(now!=fa[x] && e[i].lim<=dis[now])
            {
                while(top>1 && slope(st[top],st[top-1])<slope(now,st[top])) top--;
                st[++top]=now;
                now=fa[now];
            }
            if(top)
            {
                int l=1,r=top-1,mid,tmp=-1;
                while(l<=r)
                {
                    mid=l+r>>1;
                    if(slope(st[mid],st[mid+1])>=P[e[i].id]) tmp=mid+1,l=mid+1;
                    else r=mid-1;
                }
                if(tmp==-1) tmp=1;
                dp[e[i].id]=min(dp[e[i].id],(dis[e[i].id]-dis[st[tmp]])*P[e[i].id]+Q[e[i].id]+dp[st[tmp]]);
            }
        }
        for(int i=front[rt];i;i=nxt[i])
            solve(to[i],siz[to[i]]);
    }
    
    int main()
    {
        int t;
        read(n); read(t);
        LL s; 
        for(int i=2;i<=n;++i)
        {
             read(fa[i]); read(s); read(P[i]); read(Q[i]); read(L[i]);
             add(fa[i],i,s);
        }
        dfs(1);
        mx[0]=n+1;
        for(int i=2;i<=n;++i) dp[i]=2e17+1e12;
        solve(1,n); 
        for(int i=2;i<=n;++i) cout<<dp[i]<<'
    ';
    }
    View Code

    3672: [Noi2014]购票

    Time Limit: 30 Sec  Memory Limit: 512 MB
    Submit: 1520  Solved: 768
    [Submit][Status][Discuss]

    Description

     今年夏天,NOI在SZ市迎来了她30周岁的生日。来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会。
           全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接。为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号。其中SZ市的编号为 1。对于除SZ市之外的任意一个城市 v,我们给出了它在这棵树上的父亲城市 fv  以及到父亲城市道路的长度 sv
    从城市 v 前往SZ市的方法为:选择城市 v 的一个祖先 a,支付购票的费用,乘坐交通工具到达 a。再选择城市 a 的一个祖先 b,支付费用并到达 b。以此类推,直至到达SZ市。
    对于任意一个城市 v,我们会给出一个交通工具的距离限制 lv。对于城市 v 的祖先 a,只有当它们之间所有道路的总长度不超过 lv  时,从城市 v 才可以通过一次购票到达城市 a,否则不能通过一次购票到达。对于每个城市 v,我们还会给出两个非负整数 pv,qv  作为票价参数。若城市 v 到城市 a 所有道路的总长度为 d,那么从城市 v 到城市 a 购买的票价为 dpv+qv
    每个城市的OIer都希望自己到达SZ市时,用于购票的总资金最少。你的任务就是,告诉每个城市的OIer他们所花的最少资金是多少。
     

    Input

    第 1 行包含2个非负整数 n,t,分别表示城市的个数和数据类型(其意义将在后面提到)。输入文件的第 2 到 n 行,每行描述一个除SZ之外的城市。其中第 v 行包含 5 个非负整数 f_v,s_v,p_v,q_v,l_v,分别表示城市 v 的父亲城市,它到父亲城市道路的长度,票价的两个参数和距离限制。请注意:输入不包含编号为 1 的SZ市,第 2 行到第 n 行分别描述的是城市 2 到城市 n。

    Output

    输出包含 n-1 行,每行包含一个整数。其中第 v 行表示从城市 v+1 出发,到达SZ市最少的购票费用。同样请注意:输出不包含编号为 1 的SZ市。

    Sample Input

    7 3
    1 2 20 0 3
    1 5 10 100 5
    2 4 10 10 10
    2 9 1 100 10
    3 5 20 100 10
    4 4 20 0 10

    Sample Output


    40
    150
    70
    149
    300
    150
  • 相关阅读:
    PostgreSQL学习的九层宝塔
    MySQL和PostgreSQL在多表连接算法上的差异
    覆盖equals 时总要覆盖hashCode(9)
    覆盖equals 时总要覆盖hashCode(9)
    覆盖equals 时总要覆盖hashCode(9)
    覆盖equals 时总要覆盖hashCode(9)
    SQL Server 字段和对应的说明操作(SQL Server 2005 +)
    SQL Server 字段和对应的说明操作(SQL Server 2005 +)
    SQL Server 字段和对应的说明操作(SQL Server 2005 +)
    SQL Server 字段和对应的说明操作(SQL Server 2005 +)
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/8473073.html
Copyright © 2011-2022 走看看