【NOIP2016A组模拟7.13】搬运干草捆

题目

这里写图片描述

题解

做模拟赛时我就想了出来,然而删除节点时没有更新father,于是GG……
首先可以列出一个DP的式子 f(i,j)=min(f(i1,k))+|jhi|kj
于是我们设 g(i,j)=min(f(i1,k)),kj f(i,j)=g(i,j)+|jhi|
对于一个i来说,g(i)的图象即为:
这里写图片描述
那么 |jhi| 的图象为:
这里写图片描述
两个相加得到f:
这里写图片描述
那么g(i+1)就变成了:
这里写图片描述
由上图我们可以看出,由原先的g(i),它加上两段一次函数(y=-x+k和y=x-k)得到一个新的f,而新的f,将其所有斜率小于0的去掉,就得到了新的g。
由是,我们需要一种数据结构,来支持找到分界点所在的区间,然后分两边分别加上不同的一次函数,于是我们可以用splay来解决这个,每个节点有三元组(t,k,b)表示一条射线y=kx+b,它的左端点的横坐标为t,首先我们找出当前分界点所在的区间然后将当前点分成两个,将伸展树分裂,分别加上不同的一次函数,然后合并,查询是否有斜率小于0的射线并删除,然后要增加一条斜率为0左端点为0的射线。
注意:删点时要改father啊啊啊

贴代码:

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

#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;
    int s=0;
    bool pd=0;
    while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');
    if (ch=='-')pd=1;
    else s=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';
    if (pd)return -s;
    return s;
}

const int N = 100010;

int n;
struct point{
    LL x,k,b,ak,ab;
    int s[2];
}tree[N*2];
int fa[N*2],tot,root;
int q[N*2],k;

void down(int x){
    if (tree[x].ak!=0||tree[x].ab!=0){
        int ls=tree[x].s[0],rs=tree[x].s[1];
        if (ls){
            tree[ls].k+=tree[x].ak;
            tree[ls].b+=tree[x].ab;
            tree[ls].ak+=tree[x].ak;
            tree[ls].ab+=tree[x].ab;
        }
        if (rs){
            tree[rs].k+=tree[x].ak;
            tree[rs].b+=tree[x].ab;
            tree[rs].ak+=tree[x].ak;
            tree[rs].ab+=tree[x].ab;
        }
        tree[x].ak=tree[x].ab=0;
    }
}

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

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

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

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

int pre(int now,int v){
    if (!now)return 0;
    if (tree[now].x<=v){
        int ans=pre(tree[now].s[1],v);
        if (ans)return ans;
        return now;
    }
    return pre(tree[now].s[0],v);
}

int first(int now){
    if (!tree[now].s[0])return now;
    return kth(tree[now].s[0]);
}

void add(int now,int k,int b){
    tree[now].ak+=k;
    tree[now].ab+=b;
    tree[now].k+=k;
    tree[now].b+=b;
}

void clean(int &now){
    if (!now)return;
    down(now);
    if (tree[now].k<0){
        clean(tree[now].s[1]);
        now=tree[now].s[1];
    }
    else{
        clean(tree[now].s[0]);
        fa[tree[now].s[0]]=now;
    }
}

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

int main(){
    n=get();
    tree[root=tot=1].x=1;
    fo(i,1,n){
        int x=get();
        int u=pre(root,x),v=0;
        if (tree[u].x==x){
            if (x>1){
                v=pre(root,x-1);
                splay(u);
                split(u,0);
                splay(v);
            }
        }
        else{
            splay(u);
            tree[v=++tot]=tree[u];
            tree[v].x=x;
            fa[tree[v].s[1]]=v;
            tree[v].s[0]=tree[u].s[1]=0;
            swap(u,v);
        }
        if (!v){
            splay(u);
            add(u,1,-x);
            root=u;
        }
        else if (!u){
            splay(v);
            add(v,-1,x);
            root=v;
            u=v;
        }
        else{
            add(v,-1,x);
            add(u,1,-x);
            splay(u);
            splay(v);
            tree[u].s[0]=v;
            fa[v]=u;
        }
        clean(u);
        int w=first(u);
        splay(w);
        if (tree[w].k==0)tree[w].x=1;
        else
        if (tree[w].x>1){
            int r;
            tree[r=++tot].x=1;
            tree[r].b=tree[w].k*tree[w].x+tree[w].b;
            tree[r].k=0;
            tree[w].s[0]=r;
            fa[r]=w;
        }
        root=w;
    }
    int w=first(root);
    splay(w);
    printf("%lld\n",tree[w].k+tree[w].b);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值