BZOJ 2149 拆迁队 斜率优化DP 主席树

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2149

题目大意:
一个长度为 n 的序列a,改变其中的某些数使之成为一个单调递增序列,改变第 i 个数需要花费b[i]
求最多不改变的数的数量以及在这一条件下序列的总和 + 花费的最小值

题解:
首先可以令d[i]=a[i]i,这样可以将单调递增转化为单调不下降更方便处理
f[i] 表示到第 i 个数且不改变第i个数时最多不改变多少个数
那么 f[i]=max{f[j]}+1(d[j]d[i])
设个 g[i] 表示到第 i 个数且不改变第i个数时序列总和 + 花费的最小值
那么g[i]=min{g[j]+d[j](i1j)+(j+i)(i1j)2}+a[i](d[j]d[i]f[j]+1=f[i])
整理一下发现可以斜率优化DP
x=d[i]
y=g[i]a[i](i+1)+i(i+1)2
k=i
用一个主席树维护凸包,每次将一个点插到对应的节点中,更新时查询对应的区域

代码:

#include <bits/stdc++.h>
using namespace std;
#define MAXN 100005
typedef long long ll;
int n;
int a[MAXN];
int b[MAXN];
ll c[MAXN];
int d[MAXN];
int e[MAXN];
int f[MAXN];
ll g[MAXN];
int cnt;
struct Point
{
    ll x,y;
    Point(ll _x=0,ll _y=0):x(_x),y(_y){}
    friend ll operator*(const Point &a,const Point &b)
        {return a.x*b.y-a.y*b.x;}
};
vector<int>tr[MAXN*32];
int ls[MAXN*32],rs[MAXN*32];
int root[MAXN],tot;
void Update(int pos,int val,int l,int r,int &rt)
{
    if(!rt) rt=++tot;
    while(tr[rt].size()>1&&Point(-d[tr[rt][tr[rt].size()-1]]+d[tr[rt][tr[rt].size()-2]],c[tr[rt][tr[rt].size()-1]]-c[tr[rt][tr[rt].size()-2]])*Point(-d[val]+d[tr[rt][tr[rt].size()-1]],c[val]-c[tr[rt][tr[rt].size()-1]])<=0)tr[rt].pop_back();
    tr[rt].push_back(val);
    if(l==r) return;
    int mid=(l+r)>>1;
    if(pos<=mid) Update(pos,val,l,mid,ls[rt]);
    else Update(pos,val,mid+1,r,rs[rt]);
}
int Query(int R,int val,int l,int r,int rt)
{
    if(!rt) return -1;
    if(r<=R)
    {
        if(!tr[rt].size()) return -1;
        int ll=0,rr=tr[rt].size()-1;
        while(rr-ll>2)
        {
            int lmid=(ll+ll+rr)/3;
            int rmid=(ll+rr+rr)/3;
            if(c[tr[rt][lmid]]+1ll*val*d[tr[rt][lmid]]>c[tr[rt][rmid]]+1ll*val*d[tr[rt][rmid]]) ll=lmid;
            else rr=rmid;
        }
        int re=ll;
        for(int i=ll+1;i<=rr;i++)
            if(c[tr[rt][i]]+1ll*val*d[tr[rt][i]]<c[tr[rt][re]]+1ll*val*d[tr[rt][re]])
                re=i;
        return tr[rt][re];
    }
    int mid=(l+r)>>1;
    if(R<=mid) return Query(R,val,l,mid,ls[rt]);
    int re1=Query(R,val,l,mid,ls[rt]);
    int re2=Query(R,val,mid+1,r,rs[rt]);
    if(re1==-1) return re2;
    if(re2==-1) return re1;
    if(c[re1]+1ll*val*d[re1]<c[re2]+1ll*val*d[re2])
        return re1;
    else return re2;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",a+i);
    for(int i=1;i<=n;i++)scanf("%d",b+i);
    for(int i=1;i<=n;i++)d[i]=a[i]-i;
    f[0]=0;
    for(int i=1;i<=n;i++)
    {
        if(d[i]<0){f[i]=-1;continue;}
        if(!cnt||e[cnt]<=d[i]) e[f[i]=++cnt]=d[i];
        else e[f[i]=upper_bound(e+1,e+1+cnt,d[i])-e]=d[i];
    }
    d[0]=0;g[0]=0;c[0]=0;Update(d[0],0,0,1000000000,root[f[0]]);
    for(int i=1;i<=n;i++)
    {
        if(f[i]==-1){g[i]=0x3f3f3f3f3f3f3f3fll;continue;}
        int tmp=Query(d[i],i,0,1000000000,root[f[i]-1]);
        g[i]=c[tmp]+1ll*i*d[tmp]+1ll*i*(i-1)/2+a[i]+b[i];
        c[i]=g[i]-1ll*a[i]*(i+1)+1ll*i*(i+1)/2;
        Update(d[i],i,0,1000000000,root[f[i]]);
    }
    ll ans=0x3f3f3f3f3f3f3f3fll;
    for(int i=1;i<=n;i++)
        if(f[i]==cnt)
            ans=min(ans,g[i]+1ll*d[i]*(n-i)+1ll*(i+1+n)*(n-i)/2);
    printf("%d %lld\n",cnt,ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值