【BZOJ2149】拆迁队,分治+斜率优化DP

传送门
思路还不错的一道题目,难度不大,但是我在写的时候出了一些细节上的错误
显然题目中的两问都可以通过DP来解决
f[i] 表示保留旧房子 i 时,1i最多能保留多少个旧房子
g[i] 表示 f[i] 为最优时, 1i 的最小总花费

f[i]=max{f[j]+1}(j=0..i1,a[i]a[j]ij)

g[i]=min{(2a[j]+ij)(ij1)2+g[j]}+a[i]+b[i] ,其中 f[j] 必须是 f[i] 的最优决策点
O(n2) 的复杂度显而易见,我们来考虑优化
对于 f 的优化非常显然,分治或树状数组什么的怎样都能做,但g的转移我们还是要化简一下的
a[i]+b[i] 拿掉,我们把 g[i] 变成这个样子
i(a[j]j)+i(i1)2+(j(j+1)2ja[j]a[j]+g[j])
这样 g[i] 就变成了一个非常传统的斜率优化DP的形式了,把 i(i1)2 拿掉,每次插入一个点 (a[j]j,j(j+1)2ja[j]a[j]+g[j]) ,转移时拿斜率为 i 的直线去卡最小截距就可以了
理论问题解决了,那怎么实现呢
我们可以分治一发,处理左半边对右半边的影响时按照 a[i]i 排序,拿个splay或set维护一下凸包,查询时三分就可以了
……
可以写写试试,不过常数巨大,我们完全没必要这么做,因为观察插入点的横坐标也是 a[j]j ,也就是说排序后插入的点也是单调的,开个vector从末尾存一下,维护下凸壳就行了
(这里我也是蠢,好久都没发现插入点是单调的,差点写了cdq套set)
最终时间复杂度 O(nlog2n) ,但要说几个容易错的地方,比如long long,比如转移要从0开始,比如f,g转移时的优先顺序等等,还有一点我没注意到,但因为数据的原因只WA了一个点,就是f在转移时要注意决策点本身必须保证有解,也就是说如果决策点不是0,那么它的 f 是必须大于0的
还有就是注意按a[i]i排序以后 i <script type="math/tex" id="MathJax-Element-891">i</script>并不是单调的,所以不采用单调队列来维护和查询,而是vector/stack+三分
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#define LL long long
#define M 100005
#define mp make_pair
using namespace std;
int in()
{
    int t=0;char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch>='0'&&ch<='9') t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return t;
}
int n;
int a[M],b[M],f[M],id[M];
LL g[M];
typedef pair<int,LL>node;
vector<node> v;
bool cmp(int x,int y)
{
    if (a[x]-x==a[y]-y) return x<y;
    return a[x]-x<a[y]-y;
}
node operator -(node a,node b){return mp(a.first-b.first,a.second-b.second);}
LL operator *(node a,node b){return a.first*b.second-a.second*b.first;}
void solve(int l,int r)
{
    if (l==r) return;
    int mid=l+r>>1,cnt=0;
    solve(l,mid);
    for (int i=l;i<=r;++i) id[++cnt]=i;
    sort(id+1,id+cnt+1,cmp);
    node now;
    int mx=-1,L,R,mid1,mid2;
    for (int i=1;i<=cnt;++i)
    if (id[i]<=mid)
    {
        if (f[id[i]]<mx) continue;
        if (id[i]!=0&&!f[id[i]]) continue;
        if (f[id[i]]>mx)
            v.clear(),
            mx=f[id[i]];
        now=mp(2*a[id[i]]-2*id[i],1LL*id[i]*(id[i]+1)-1LL*2*id[i]*a[id[i]]-2*a[id[i]]+2*g[id[i]]);
        while (v.size()>1&&(v[v.size()-1]-v[v.size()-2])*(now-v[v.size()-1])<=0) v.pop_back();
        v.push_back(now);
    }
    else
    {
        if (f[id[i]]>mx+1) continue;
        if (f[id[i]]<mx+1) g[id[i]]=1e18;
        f[id[i]]=mx+1;
        L=0,R=v.size()-1;
        for (;R-L>2;)
        {
            mid1=(R-L)/3+L;
            mid2=(R-L)/3*2+L;
            if (1LL*v[mid1].first*id[i]+v[mid1].second>1LL*v[mid2].first*id[i]+v[mid2].second) L=mid1+1;
            else R=mid2-1;
        }
        for (int j=L;j<=R;++j)
            g[id[i]]=min(g[id[i]],(1LL*v[j].first*id[i]+1LL*id[i]*(id[i]-1)+v[j].second)/2+a[id[i]]+b[id[i]]);
    }
    solve(mid+1,r);
}
main()
{
    n=in();
    for (int i=1;i<=n;++i) a[i]=in();
    for (int i=1;i<=n;++i) b[i]=in();
    for (int i=1;i<=n;++i) g[i]=1e18;
    solve(0,n);
    int maxn=0;
    LL ans;
    for (int i=1;i<=n;++i)
    {
        g[i]+=1LL*(n-i)*(a[i]+1)+1LL*(n-i)*(n-i-1)/2;
        if (maxn<f[i]) maxn=f[i],ans=g[i];
        else if (maxn==f[i]) ans=min(ans,g[i]);
    }
    printf("%d %lld\n",maxn,ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值