Codeforces Gym 101234E Lines Game

可以发现,我们一定会选择 i pi都递增的若干个位置,而且一个方案合法的充要条件是对于任意两个相邻的选择 i j,不存在 k 满足i<k<j pi<pk<pj 。为了方便增加 p0=0 pn+1=n+1 两个位置,设 dpi 为最靠右的一个选择为 i 的最小花费,那么我们可以列出一个O(n2)的dp。

dpi=vi+maxi,jdpj

这个dp可以用分治优化。考虑左边对右边的贡献,把每个位置按照 pi 递增的顺序加入,在左边维护一个 i 递减的单调栈用来更新答案【i递减是因为如果出现了 i<i pi<i i 不能用来更新答案】,在右边维护一个 i 递增的单调栈【i递增是因为如果出现了 i>i pi<i ,那么 i i 和之后的元素都没有影响】。建立一棵关于pi的线段树,在左边用dp值进行单点修改,在右边在合法的区间内【即大于栈顶的 pi 】进行区间查询最小值更新答案。因为排序和线段树的复杂度都是 O(nlogn) ,所以总的复杂度 O(nlog2n)

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=200010,maxt=4000010,oo=2e9+1000;
int n,a[maxn],v[maxn],dp[maxn],mn[maxt],f[maxn],g[maxn],s1[maxn],s2[maxn];
int cmp(int x,int y)
{
    return a[x]<a[y];
}
void modi(int u,int L,int R,int p,int x)
{
    if (L==R)
    {
        mn[u]=x;
        return;
    }
    int mid=(L+R)/2;
    if (p<=mid) modi(u*2,L,mid,p,x);
    else modi(u*2+1,mid+1,R,p,x);
    mn[u]=min(mn[u*2],mn[u*2+1]);
}
int qry(int u,int L,int R,int l,int r)
{
    if (l<=L&&R<=r) return mn[u];
    int ret=oo,mid=(L+R)/2;
    if (l<=mid) ret=min(ret,qry(u*2,L,mid,l,r));
    if (r>mid) ret=min(ret,qry(u*2+1,mid+1,R,l,r));
    return ret;
}
void solve(int l,int r)
{
    if (l==r) return;
    int mid=(l+r)/2,t1=0,t2=0;
    solve(l,mid);
    for (int i=l;i<=mid;i++) f[i]=i;
    for (int i=mid+1;i<=r;i++) g[i]=i;
    sort(f+l,f+mid+1,cmp);
    sort(g+mid+1,g+r+1,cmp);
    for (int i=l,j=mid+1;i<=mid||j<=r;)
        if (i<=mid&&(j>r||a[f[i]]<a[g[j]]))
        {
            while (t1&&s1[t1]<f[i])
            {
                modi(1,0,n,a[s1[t1]],oo);
                t1--;
            }
            s1[++t1]=f[i];
            modi(1,0,n,a[f[i]],dp[f[i]]);
            i++;
        }
        else
        {
            while (t2&&s2[t2]>g[j]) t2--;
            dp[g[j]]=min(dp[g[j]],qry(1,0,n,t2?a[s2[t2]]+1:0,a[g[j]])+v[g[j]]);
            s2[++t2]=g[j];
            j++;
        }
    while (t1) modi(1,0,n,a[s1[t1--]],oo);
    solve(mid+1,r);
}
void build(int u,int L,int R)
{
    mn[u]=oo;
    if (L==R) return;
    int mid=(L+R)/2;
    build(u*2,L,mid);
    build(u*2+1,mid+1,R);
}
int main()
{
    //freopen("i.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++) scanf("%d",&v[i]);
    n++;
    a[n]=n;
    build(1,0,n);
    for (int i=1;i<=n;i++) dp[i]=oo;
    solve(0,n);
    printf("%d\n",dp[n]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值