[JZOJ4940]前鬼后鬼的守护/[JZOJ4623]搬运干草捆

博客介绍了[JZOJ4940]和[JZOJ4623]两道题目的解决方案,重点探讨了动态规划(DP)策略和数据结构在优化算法中的应用。在60分的解法中,通过直接DP解决;100分的解法中,利用线段树或Splay维护下凸壳,并分析了两种特殊情况:当xi大于或小于最大pi时的不同处理策略,最终实现了O(nlogn)的时间复杂度解法。
摘要由CSDN通过智能技术生成

题目大意

给定一个 n 个数的序列{x1,...,xn},你需要计算一个新的序列 {a1,...,an}
其中 1i<n,aiai+1 。你需要最小化 i=1|aixi| ,输出这个值。

1n5×105,xi109


题目分析

60分

首先显然一定有一种最优解使得 1in,ai{x}
那么我们直接 dp ,设 fi,j 表示填到 ai ai 的取值在 {x} 中排名为 j 的最优答案。可以写出:

fi,j=minkj{fi1,k}+|xkthjxi|

处理前缀 min 之后,这个可以 O(n2) 转移。

100分

Algorithm 1

gi,j 表示 fi,j 关于 j 的前缀min
可以发现如果将 gi,j 看成一个以 j 为自变量的函数(i不变),则其一定是由若干条直线构成的连续函数,并且这些直线一定是若干条单调递减的直线加上最后面一条平行于 x 轴的平板。并且显然它一定是凸的。当前的答案就是平板的y坐标。
g函数示意
为什么呢?观察 dp 方程, g 函数一定是由原本的g函数,加上一个形如 y=|xa| 的绝对值函数,再将末端若干条斜率大于 0 的线段改为和函数最低点纵坐标相同的平板(因为是前缀min)。
那么我们考虑模拟这个不断加入绝对值函数的过程。
加入函数
如上图,在 x=4 之前的所有线段都是单调递减的,在这段区间内绝对值函数也是单调递减的,因此它们的相对形态不会发生变化。但是在 x=4 这个横坐标这里,线段 DE 被分成了斜率不相等的两部分,并且从此往后,有些函数可能会被加成斜率为正的函数。因为函数的斜率是递增的,所以我们可以二分出第一条斜率为正的线段,然后将这个线段以及后面所有线段都变成同一个平板,它的纵坐标就是第一条斜率为正的线段的左端点的纵坐标。
使用线段树或者 splay 等数据结构来维护这个“下凸壳”,记录每条线段的斜截式方程就好了。
时间复杂度 O(nlogn)

Algorithm 2

然而还有更加方便的算法。
可以发现,我们的凸壳可以通过这样的方式表示:
有若干个点 (pi,0) ,定义函数

gi(x)=x+pi,0,xpix>pi

令凸壳为 f(x) ,那么 f(x)=ans+ni=1gi(x) ans 为最后平板的纵坐标,也是当前的答案。
你也可以理解为这些点每个点向左上引出一条斜率为 1 的射线,然后水平向右引出一条射线组成若干个函数,这些函数的和再加上平板的高度。
因此,只要我们知道这些关键点,再知道平板的高度(即当前的答案),我们就可以知道这个凸壳的具体形态。
考虑插入一个数 xi 的时候会发生什么,首先显然它会给 f(x) 引入一条从 (xi,0) 向左上角射出的 45 的射线,那么它右边往上翘的部分怎么处理呢?首先显然平板左端点一定是 max{pi} 分两种情况讨论:

Case 1: ximax{pi}

这说明了什么呢?我当前插入的绝对值函数拐点在平板上。
显然这条线的加入并不会造成平板以及(整个函数)的抬升。而且这样的话 xi 的绝对值函数向右上角翘的那一部分就没有起任何作用,因为它比平板高,会被取 min 变成平板。因此我们不需要做任何处理。

Case 2: xi<max{pi}

这种情况有点麻烦。
首先它一定会使平板抬升,抬升多少呢?显然是 max{pi}xi ,即平板左端点上升的高度。
其次, xi 的绝对值函数向右上角引出的 45 的射线会和原本平板左端点向左上角引出的 45 射线的效果相叠加,变成一条水平线,因此原本平板左端点向左上角引出的射线和 xi 向右上角引出的射线被一条从 xi 向左上角引出的 45 射线等效代替了。我们将 xi 加入,原端点删掉就好了。

整个过程只需要使用堆来维护最大的 {pi} 。总时间复杂度 O(nlogn) ,代码短得过分。


代码实现

先贴一份 splay 实现的,@Samjia2000

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<set>
#include<map>

#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

typedef long long LL;
typedef double db;

int get(){
    char ch;
    while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');
    if (ch=='-'){
        int s=0;
        while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
        return -s;
    }
    int s=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
    return s;
}

const int N = 500010;
const int INF = 1.1e+9;

struct point{
    int w,k,adk;
    LL b,adb;
    int s[2];
}tree[N];
int fa[N];
int tot,root,n;

void add(int x,int adk,LL adb){
    tree[x].k+=adk;
    tree[x].b+=adb;
    tree[x].adk+=adk;
    tree[x].adb+=adb;
}

void down(int now){
    if (tree[now].adk==0&&tree[now].adb==0)return;
    int x=tree[now].s[0];
    if (x)add(x,tree[now].adk,tree[now].adb);
    x=tree[now].s[1];
    if (x)add(x,tree[now].adk,tree[now].adb);
    tree[now].adk=tree[now].adb=0;
}

int findle(int now,int v){
    if (!now)return 0;
    down(now);
    if (tree[now].w==v)return now;
    if (tree[now].w>v)return findle(tree[now].s[0],v);
    else{
        int o=findle(tree[now].s[1],v);
        if (o)return o;
        return now;
    }
}

int findlg(int now,int v){
    if (!now)return 0;
    down(now);
    if (tree[now].w>=v)return findlg(tree[now].s[0],v);
    else{
        int o=findlg(tree[now].s[1],v);
        if (o)return o;
        return now;
    }
}

int getlst(int now){
    down(now);
    if (!tree[now].s[1])return now;
    return getlst(tree[now].s[1]);
}

void split(int x,int t){
    down(x);
    fa[tree[x].s[t]]=0;
    tree[x].s[t]=0;
}

int k,a[N];

void clear(int x){
    k=0;
    while(x){
        a[++k]=x;
        x=fa[x];
    }
    fd(i,k,1)down(a[i]);
}

int pd(int x){
    return tree[fa[x]].s[1]==x;
}

void rotate(int x){
    int y=fa[x],z=fa[y];
    if (z){int t=pd(y);tree[z].s[t]=x;}
    int t=pd(x);
    if (tree[x].s[t^1])fa[tree[x].s[t^1]]=y;
    tree[y].s[t]=tree[x].s[t^1];
    tree[x].s[t^1]=y;
    fa[x]=z;fa[y]=x;
}

void splay(int x,int t){
    clear(x);
    while(fa[x]!=t){
        if (fa[fa[x]]){
            if (pd(x)==pd(fa[x]))rotate(fa[x]);
            else rotate(x);
        }
        rotate(x);
    }
}

int main(){
    freopen("chen.in","r",stdin);
    freopen("chen.out","w",stdout);
    n=get();
    tot=root=1;
    fo(i,1,n){
        int v=get();
        int x=findle(root,v),y;
        if (tree[x].w==v){
            y=findlg(root,v);
            swap(x,y);
            splay(y,0);
            split(y,0);
            splay(x,0);
        }
        else{
            y=++tot;
            splay(x,0);
            int z=tree[x].s[1];
            split(x,1);
            tree[y].w=v;
            tree[y].k=tree[x].k;
            tree[y].b=tree[x].b;
            tree[y].s[1]=z;
            fa[z]=y;
        }
        add(x,-1,v);
        add(y,1,-v);
        down(x);
        tree[x].s[1]=y;
        fa[y]=x;
        int t=getlst(x);
        root=x;
        if (tree[t].k>0){
            splay(t,0);
            root=t;
            int t_=findlg(root,tree[t].w);
            if (tree[t_].k==0){
                splay(t_,0);
                split(t_,1);
                root=t_;
            }
            else{
                tree[t].b=LL(tree[t].k)*tree[t].w+tree[t].b;
                tree[t].k=0;
            }
        }
    }
    printf("%lld\n",tree[getlst(root)].b);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

再来一份堆的做法来做对比

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <set>

using namespace std;

typedef long long LL;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=50050;

multiset<int> t;
LL ans;
int n;

int main()
{
    freopen("chen.in","r",stdin),freopen("chen.out","w",stdout);
    n=read();
    for (int i=1,x;i<=n;i++)
    {
        x=read();
        t.insert(x);
        if (x<*t.rbegin())
        {
            ans+=(*t.rbegin())-x;
            multiset<int>::iterator it=t.end();
            t.erase(--it),t.insert(x);
        }
    }
    printf("%lld\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值