洛谷P3994 Highway(树形DP+斜率优化+可持久化线段树/二分)

  有点类似NOI2014购票

  首先有方程$f(i)=min\{f(j)+(dep_i-dep_j)*p_i+q_i\}$

  这个显然是可以斜率优化的...

  $\frac {f(j)-f(k)}{dep_j-dep_k}<p_i$

  $p_i$是单调的,于是可以单调队列,当遍历完一个子树的时候,必须复原单调队列到进入这棵子树前的样子,这个用可持久化线段树维护可持久化数组显然可做...

  当然有更聪明的方法。

  单调队列队头出去的时候实际上队列信息不会被覆盖,于是恢复左端点只要记录进入当前点前的左端点即可。

  右端点可能会被覆盖,但是每次最多覆盖一个点(也就是当前点),于是恢复右端点只需要记录进入当前点前的右端点和被覆盖的值即可。

  但是这么做的话无法保证一个点出队入队次数是常数级别的,也就无法保证复杂度是$O(n)$了,所以每次不能一个一个出队(一条链加一朵大菊花就可以卡了),必须二分出队位置,才可以保证复杂度,做到$O(nlogn)$。

  但是出题人没有卡暴力出队的做法...我写二分比直接暴力弹出跑得快= =... 事实证明是评测机玄学...有时候跑得快有时候跑得慢

二分:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=1000010, inf=1e9;
struct poi{int too, dis, pre;}e[maxn];
int n, x, z, tot, l, r;
int p[maxn], Q[maxn], last[maxn], nowl[maxn], nowr[maxn], nowqr[maxn], q[maxn];
ll f[maxn], d[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar();
    while(c<='9' && c>='0') k=k*10+c-'0', c=getchar();
    k*=f;
}
inline void add(int x, int y, int z){e[++tot]=(poi){y, z, last[x]}; last[x]=tot;}
inline double xl(int j, int k){return 1.0*(f[j]-f[k])/(d[j]-d[k]);}
inline void dfs(int x)
{
    nowl[x]=l; 
    if(l<r)
    {
        int L=l, R=r-1;
        while(L<R)
        {
            int mid=(L+R)>>1;
            if(p[x]-xl(q[mid+1], q[mid])>1e-9) L=mid+1;
            else R=mid;
        }
        l=(p[x]-xl(q[L], q[L+1])>1e-9)?L+1:L; 
    }
    nowr[x]=r;
    if(x!=1) f[x]=f[q[l]]+Q[x]+1ll*(d[x]-d[q[l]])*p[x];
    if(l<r)
    {
        int L=l+1, R=r;
        while(L<R)
        {
            int mid=(L+R+1)>>1;
            if(xl(x, q[mid])<xl(q[mid], q[mid-1])) R=mid-1;
            else L=mid;
        }
        r=(xl(x, q[L])<xl(q[L-1], q[L]))?L-1:L; 
    }
    nowqr[x]=q[r+1]; q[++r]=x;
    for(int i=last[x], too;i;i=e[i].pre) d[too=e[i].too]=d[x]+e[i].dis, dfs(too); 
    l=nowl[x]; q[r]=nowqr[x]; r=nowr[x];
}
int main()
{
    read(n);
    for(int i=2;i<=n;i++) read(x), read(z), add(x, i, z), read(p[i]), read(Q[i]);
    l=1; r=0; dfs(1);
    for(int i=2;i<=n;i++) printf("%lld\n", f[i]);
}
View Code

暴力出队:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define ll long long
using namespace std;
const int maxn=1000010, inf=1e9;
struct poi{int too, dis, pre;}e[maxn];
int n, x, z, tot, l, r;
int p[maxn], Q[maxn], last[maxn], nowl[maxn], nowr[maxn], nowqr[maxn], q[maxn];
ll f[maxn], d[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<'0' || c>'9') c=='-' && (f=-1), c=getchar();
    while(c<='9' && c>='0') k=k*10+c-'0', c=getchar();
    k*=f;
}
inline void add(int x, int y, int z){e[++tot]=(poi){y, z, last[x]}; last[x]=tot;}
inline double xl(int j, int k){return 1.0*(f[j]-f[k])/(d[j]-d[k]);}
inline void dfs(int x)
{
    nowl[x]=l; while(l<r && p[x]-xl(q[l+1], q[l])>1e-9) l++;
    if(x!=1) f[x]=f[q[l]]+Q[x]+1ll*(d[x]-d[q[l]])*p[x];
    nowr[x]=r; while(l<r && xl(x, q[r])<xl(q[r], q[r-1])) r--;
    nowqr[x]=q[r+1]; q[++r]=x;
    for(int i=last[x], too;i;i=e[i].pre) d[too=e[i].too]=d[x]+e[i].dis, dfs(too); 
    l=nowl[x]; q[r]=nowqr[x]; r=nowr[x];
}
int main()
{
    read(n);
    for(int i=2;i<=n;i++) read(x), read(z), add(x, i, z), read(p[i]), read(Q[i]);
    l=1; r=0; dfs(1);
    for(int i=2;i<=n;i++) printf("%lld\n", f[i]);
}
View Code

 

转载于:https://www.cnblogs.com/Sakits/p/8196237.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值