【AtCoder Regular Contest 066 F】【JZOJ 5451】Contest with Drinks Hard

9 篇文章 0 订阅
5 篇文章 0 订阅

Decsription

有一个长度为n的数列,对于每个位置,你可以选/不选,选第i个位置的代价为a[i]
假设c[i]=0/1表示第i个位置选/不选,那么最后的收益定义为:
(ni=1nj=i(jk=ick))(ni=1ciai)
接下来有m个询问,形如x y,表示将a[x]改变成y后的最大收益。改变无后效性。
这里写图片描述

Analysis

先不考虑询问,如果直接求最大收益怎么做?
显然可以设f[i]表示做到第i个位置的最大收益,然后发现dp可以斜率优化,这样就O(n)了
考虑询问,如果a[x]变成了y,如果不选a[x],答案肯定没有影响
我们只要从头,从尾各做一次dp,那么不选的答案就是f[x-1]+g[x+1]
如果必定要选,考虑求出h[x]表示必选x位置的答案,那么选的答案就是h[x]-a[x]+y了
可以发现
h[x]=maxixj(f[i1]+g[j+1]+(ji+1)(ji+2)/2(sjsi1))
这个和上面dp很像,由于有i,j两个变量,考虑固定i,求出最优的j
自己手动拆一下,转移可以写成形如
h[x]=max(Fi+Gjij)
这时候可以套用上面的斜率优化
但是我们不可能同时枚举i,j,那么考虑分治,每次对于分治区间[l,r],处理l<=i<=mid<=j<=r的i,j,考虑枚举 i <script type="math/tex" id="MathJax-Element-176">i</script>,预处理出可能成为最优决策的j,那么随着i右移,j必然左移,更新此时的h[i],这样能更新[l,mid]的h[x]
然后反过来做一次,更新[mid+1,r]的h[x]
本题可以O(nlogn)解决

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define fd(i,a,b) for(ll i=a;i>=b;i--)
#define efo(i,v,u) for(int i=last[v],u=to[last[v]];i;i=next[i],u=to[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
void read(ll &n)
{
    ll t=0,p=1;char ch;
    for(ch=getchar();!('0'<=ch && ch<='9');ch=getchar())
        if(ch=='-') p=-1;
    for(;'0'<=ch && ch<='9';ch=getchar()) t=t*10+ch-'0';
    n=t*p;
}
const int N=6e5+5;
int n,sig,top;
ll a[N],s[N],f[N],g[N],h[N],F[N],G[N],sta[N];
void rev(ll *arr)
{
    fo(i,1,n/2) swap(arr[i],arr[n-i+1]);
    fo(i,1,n) s[i]=s[i-1]+a[i];
}
ll H(ll j){return 2*(sig==0?f[j]:g[j])+2*s[j]+j*j-j;}
db xl(int j,int k){return 1.0*(H(j)-H(k))/(j-k);}
ll S(ll x){return x*(x+1)/2;}
ll dp(ll *d) // j as a point
{
    mset(d,0);mset(sta,0);
    sta[top=1]=0;
    fo(i,1,n)
    {
        while(top && xl(sta[top-1],sta[top])<=2*i) top--;
        int j=sta[top];
        d[i]=max(d[i-1],d[j]+S(i-j)-s[i]+s[j]);
        while(top && xl(sta[top-1],sta[top])<=xl(sta[top],i)) top--;
        sta[++top]=i;
    }
}
db cross(ll k1,ll b1,ll k2,ll b2)
{
    return 1.0*(b2-b1)/(k1-k2);
}
void solve(int l,int r)
{
    if(l==r)
    {
        h[l]=max(h[l],f[l-1]+g[l+1]+1-a[l]);
        return;
    }
    int mid=l+r>>1;
    fo(i,l,mid) F[i]=f[i-1]+s[i-1]+((ll)i*i-3*i)/2+1;
    fo(j,mid+1,r) G[j]=g[j+1]-s[j]+((ll)j*j+3*j)/2;
    top=0;
    //j as a line
    fd(j,r,mid+1)
    {
        while(top && G[j]>=G[sta[top]]) top--;
        while(top>1 && cross(-j,G[j],-sta[top],G[sta[top]])<=cross(-sta[top],G[sta[top]],-sta[top-1],G[sta[top-1]]))
            top--;
        sta[++top]=j;
    }
    int p=1;ll t=-1e16;
    fo(i,l,mid)
    {
        while(p<top && G[sta[p]]-sta[p]*i<=G[sta[p+1]]-sta[p+1]*i) p++;
        t=max(t,F[i]+G[sta[p]]-i*sta[p]);
        h[i]=max(h[i],t);
    }
    solve(l,mid);solve(mid+1,r);
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n) read(a[i]),s[i]=s[i-1]+a[i];
    sig=0;dp(f);
    rev(a);sig=1;dp(g);rev(g);rev(a);
    mset(h,128);
    rev(a);
    rev(f);rev(g);swap(f,g);rev(h);
    solve(1,n);
    rev(h);swap(f,g);rev(g);rev(f);
    rev(a);
    solve(1,n);
    int Q,x,y;
    scanf("%d",&Q);
    fo(i,1,Q)
    {
        scanf("%d %d",&x,&y);
        printf("%lld\n",max(f[x-1]+g[x+1],h[x]+a[x]-y));
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值