[BZOJ3672][NOI2014]购票(斜率优化DP+树链剖分+线段树+三分)

39 篇文章 1 订阅
37 篇文章 0 订阅

题目:

我是超链接

题解:

这个很明显要从上往下DP,而且下面的DP选择了就可以直接选择用上面的信息直接转移了。

大概就是 f[i]=min(p[i](dis[i]dis[j])+q[i]+f[j]) f [ i ] = m i n ( p [ i ] ∗ ( d i s [ i ] − d i s [ j ] ) + q [ i ] + f [ j ] ) (dis[i]-dis[j]<=l[i],j=fa[j])

dis[i]当然是距离数组,f[i]就是答案数组啦
按照无关的量放在外面画柿子吧, f[i]=min(p[i]dis[j]+f[j])+p[i]dis[i]+q[i] f [ i ] = m i n ( − p [ i ] ∗ d i s [ j ] + f [ j ] ) + p [ i ] ∗ d i s [ i ] + q [ i ]
那么对于一个x=p[i]的情况,k=-dis[j],b=f[j],转化为y坐标最小。这样对于fa[i]到pre[i](最高满足条件的祖先),我们有这些直线,这些直线肯定不会都有用啊,其实是要维护一个下凸壳,然后选择出在横坐标为p[i]的情况下最小值所在的直线就ok了。

假设我们已经有一个半平面和横坐标x,那么我们可以在O(logn)的时间内找到使得y最小的那条线(三分)

既然是树,那么我们可以考虑一下树链剖分,每条树链用一个线段树维护一下树链上各个区间半平面交的结果,由于从祖先到子孙的dis[i]会递增,所以我们有一个天然的斜率顺序

所以这个可以用vector维护。

代码:

因为要会考就先咕了代码叭QAQ
先弄个正确的代码上来叭

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define ls x << 1
#define rs x << 1 | 1
#define eb emplace_back
#define pb pop_back
#define db double
const int MAX = 210033;
const ll inf = (1ll << 63) - 1;
struct Point
{
    ll x, y;
    Point() {}
    Point(ll a, ll b) { x = a, y = b; }
    friend Point operator - (Point a, Point b) { return Point(a.x - b.x, a.y - b.y); }
    friend ll operator * (Point a, Point b) { return a.x * b.y - a.y * b.x; }
};
struct edge
{
    int to; ll w;
    edge() {}
    edge(int a, ll b) { to = a, w = b; }
};
vector<edge> e[MAX];
vector<Point> h[MAX << 2];
int n, cnt_dfn;
int dfn[MAX], top[MAX], inv[MAX], siz[MAX], fa[MAX], que[MAX];
ll ans[MAX], P[MAX], Q[MAX], lim[MAX], dis[MAX];
void dfs1(int u)
{
    siz[u] = 1;
    for (auto v : e[u])
    {
        dis[v.to] = dis[u] + v.w;
        dfs1(v.to);
        siz[u] += siz[v.to];
    }
}
void dfs2(int u, int tp)
{
    int big = 0;
    top[u] = tp, dfn[u] = ++cnt_dfn, inv[cnt_dfn] = u;
    for (auto v : e[u])
        if (siz[v.to] > siz[big])
            big = v.to;
    if (!big) return;
    dfs2(big, tp);
    for (auto v : e[u])
        if (v.to != big)
            dfs2(v.to, v.to);
}
void bfs()
{
    int head, tail;
    que[head = tail = 1] = 1;
    while (head <= tail)
    {
        int u = que[head++];
        for (auto v : e[u])
            que[++tail] = v.to;
    }
}
void update(int x, int l, int r, int p, Point c)
{
    int siz = 0;
    while ((siz = h[x].size()) > 1 && (c - h[x][siz - 2]) * (h[x][siz - 1] - h[x][siz - 2]) > 0)
        h[x].pb();
    h[x].eb(c);
    if (l >= r) return;
    int mid = (l + r) >> 1;
    if (p <= mid) update(ls, l, mid, p, c);
    else update(rs, mid + 1, r, p, c);
}
ll query(int x, int l, int r, int L, int R, int id)
{
    ll Ans = inf;
    if (L <= l && r <= R)
    {
        int lx = 0, rx = h[x].size() - 1;
        while (rx - lx > 3)
        {
            int m1 = lx + (rx - lx) / 3, m2 = rx - (rx - lx) / 3;
            ll v1 = P[id] * (dis[id] - h[x][m1].x) + h[x][m1].y;
            ll v2 = P[id] * (dis[id] - h[x][m2].x) + h[x][m2].y;
            if (v1 <= v2) rx = m2;
            else lx = m1;
        }
        for (int i = lx; i <= rx; i++)
            Ans = min(Ans, P[id] * (dis[id] - h[x][i].x) + h[x][i].y + Q[id]);
        return Ans;
    }
    int mid = (l + r) >> 1;
    if (L <= mid) Ans = min(Ans, query(ls, l, mid, L, R, id));
    if (R > mid) Ans = min(Ans, query(rs, mid + 1, r, L, R, id));
    return Ans;
}
void calc(int L, int R, int id)
{
    ll Ans = inf;
    while (dis[top[R]] > dis[L])
    {
        Ans = min(Ans, query(1, 1, n, dfn[top[R]], dfn[R], id));
        R = fa[top[R]];
    }
    Ans = min(Ans, query(1, 1, n, dfn[L], dfn[R], id));
    ans[id] = Ans;
    update(1, 1, n, dfn[id], Point(dis[id], ans[id]));
}
main()
{
    scanf("%lld%lld", &n, &fa[MAX - 1]);
    for (int i = 2; i <= n; i++)
    {
        ll w;
        scanf("%lld%lld%lld%lld%lld", &fa[i], &w, &P[i], &Q[i], &lim[i]);
        e[fa[i]].eb(edge(i, w));
    }
    dfs1(1), dfs2(1, 1), bfs();
    update(1, 1, n, 1, Point(0, 0));
    for (int i = 2; i <= n; i++)
    {
        int anc = que[i], dist = dis[anc] - lim[anc];
        anc = fa[anc];
        while (anc > 1 && dis[fa[top[anc]]] >= dist)
            anc = fa[top[anc]];
        if (anc > 1)
        {
            int l = dfn[top[anc]], r = dfn[anc];
            while (l <= r)
            {
                int mid = (l + r) >> 1;
                if (dis[inv[mid]] >= dist)
                    anc = inv[mid], r = mid - 1;
                else l = mid + 1;
            }
        }
        else anc = 1;
        calc(anc, fa[que[i]], que[i]);
    }
    for (int i = 2; i <= n; i++)
        printf("%lld\n", ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值