[AGC040E]Prefix Suffix Addition

Prefix Suffix Addition

题解

首先,对于这道题,我们一个比较容易的想法是将整个序列拆成一个序列 B B B和一个序列 C C C,满足 A i = B i + C i A_i=B_i+C_i Ai=Bi+Ci
我们对于序列 B B B中的数执行操作 1 1 1,序列 C C C中的数执行操作 2 2 2
这样,我们就能将对一个序列进行两种操作变为对两个序列执行方向不同的一种操作。
我们观察一个序列的情况,如果我们被允许从一端开始减去一个递增序列最少多少次能把它减完。
可以发现,这样减完序列 A A A的答案为 ∑ i = 1 n [ A i > A i + 1 ] \sum_{i=1}^{n} [A_{i}>A_{i+1}] i=1n[Ai>Ai+1]
显然,我们会尝试从 a n a_n an a 1 a_1 a1,不断把数减为 0 0 0,在 a i + 1 a_{i+1} ai+1减到 0 0 0时, a i a_i ai被减的值不会超过 a i + 1 a_{i+1} ai+1。如果 a i + 1 ⩾ a i a_{i+1}\geqslant a_i ai+1ai,那么减完 a i + 1 a_{i+1} ai+1时也可以把 a i a_i ai减完,否则需要多执行一次操作。
反过来操作同理,所以最后的答案为 ∑ i = 0 n [ B i > B i + 1 ] + [ C i < C i + 1 ] \sum_{i=0}^{n} [B_i>B_{i+1}]+[C_i<C_{i+1}] i=0n[Bi>Bi+1]+[Ci<Ci+1]
我们可以考虑对这东西进行 d p dp dp
定义 d p i , j dp_{i,j} dpi,j表示 B i = A i − j , C i = j B_i=A_i-j,C_i=j Bi=Aij,Ci=j时前缀所产生的最小贡献。
显然,我们是比较容易地写出它的 d p dp dp转移式的,
d p i , j = min ⁡ k = 0 A i − 1 ( d p i − 1 , k + [ j > k ] + [ A i − j < A i − 1 − k ] ) dp_{i,j}=\min_{k=0}^{A_{i-1}}\left(dp_{i-1,k}+[j>k]+[A_i-j<A_{i-1}-k]\right) dpi,j=k=0minAi1(dpi1,k+[j>k]+[Aij<Ai1k])
当然,直接这样转移是 O ( n A ) O\left(nA\right) O(nA)的,显然不行,考虑优化。
我们可以从上面的转移式发现一个性质:

  • ∀ j < k ( j , k ∈ [ 0 , A i ] )   ,   d p i , k − d p i , j ∈ [ 0 , 2 ] \forall j<k(j,k\in[0,A_{i}])\,,\,dp_{i,k}-dp_{i,j}\in[0,2] j<k(j,k[0,Ai]),dpi,kdpi,j[0,2]

这相当于说它是单调不降的并且最大值与最小值差不会超过 2 2 2
单调不降这点是比较好理解的,对于 j < k ( j , k ∈ [ 0 , A i ] ) j<k(j,k\in [0,A_i]) j<k(j,k[0,Ai]),如果 k k k的最优转移点为 p k p_k pk的话,那么我们先假令 j j j也从 p k p_k pk转移过来有:
[ j > p k ] + [ A i − j < A i − 1 − p k ] ⩽ [ k > p k ] + [ A i − k < A i − 1 − p k ] ⇔ [ j > p k ] − [ k > p k ] + [ A i − j < A i − 1 − p k ] − [ A i − k < A i − 1 − p k ] ⩽ 0 ⇔ [ j ⩽ p k < k ] + [ A i − k ⩽ A i − 1 − p k ⩽ A i − j ] ⩾ 0 [j>p_k]+[A_{i}-j<A_{i-1}-p_k]\leqslant [k>p_k]+[A_i-k<A_{i-1}-p_k]\\ \Leftrightarrow[j>p_k]-[k>p_k]+[A_{i}-j<A_{i-1}-p_k]-[A_i-k<A_{i-1}-p_k]\leqslant 0\\ \Leftrightarrow[j\leqslant p_k<k]+[A_i-k\leqslant A_{i-1}-p_{k}\leqslant A_i-j]\geqslant0 [j>pk]+[Aij<Ai1pk][k>pk]+[Aik<Ai1pk][j>pk][k>pk]+[Aij<Ai1pk][Aik<Ai1pk]0[jpk<k]+[AikAi1pkAij]0显然,这个等式是恒成立的。
j j j还可以有更好的转移点,所以还可能更小。
而差值不超过 2 2 2这点也非常好理解,如果 j j j是从 p j p_j pj转移过来,那么如果 k k k也从这转移的话,最多就多两个 1 1 1,加起来显然不会超过 2 2 2
所以,事实上我们的 d p j dp_j dpj是一个分三段的分段函数,我们的转移实际上就是在对这个分段函数进行转移。
我们可以记录 ( x i , l i , r i ) (x_i,l_i,r_i) (xi,li,ri)表示这个三元组,表示对于 d p i , j = x + [ j > l i ] + [ j > r i ] dp_{i,j}=x+[j>l_i]+[j>r_i] dpi,j=x+[j>li]+[j>ri]
转移到 d p i + 1 dp_{i+1} dpi+1后的最小值 x x x仍然可以在 d p i + 1 , 0 dp_{i+1,0} dpi+1,0处取得,我们考虑计算 d p i + 1 , 0 dp_{i+1,0} dpi+1,0的值。
显然 d p i , 0 ⩽ d p i + 1 , 0 ⩽ d p i , 0 + 1 dp_{i,0}\leqslant dp_{i+1,0}\leqslant dp_{i,0}+1 dpi,0dpi+1,0dpi,0+1,只有 [ 0 > k ] [0>k] [0>k]显然不成立,只有 [ A i − 0 ⩽ A i − 1 − k ] [A_i-0\leqslant A_{i-1}-k] [Ai0Ai1k]会贡献到答案。
对于 d p i dp_i dpi这一层,最好的转移点显然是 d p i , l i dp_{i,l_i} dpi,li d p i , r i dp_{i,r_i} dpi,ri,它们在自己的层次里都使得 C i C_i Ci最大。
对于 d p i + 1 , 0 dp_{i+1,0} dpi+1,0,我们就只看 d p i , l dp_{i,l} dpi,l,有 d p i + 1 , 0 = d p i , 0 + [ A i < A i − 1 − l i ] dp_{i+1,0}=dp_{i,0}+[A_i<A_{i-1}-l_i] dpi+1,0=dpi,0+[Ai<Ai1li],这样就得到了 x i + 1 x_{i+1} xi+1
之后的 l i + 1 , r i + 1 l_{i+1},r_{i+1} li+1,ri+1可以用类似的方法得到,我们就相对于上面的每一段,建立出关于 l i + 1 l_{i+1} li+1 r i + 1 r_{i+1} ri+1的不等式组,解出答案。
容易发现,这样的转移单次是 O ( 1 ) O\left(1\right) O(1)的,答案就是 x n + [ a n > l n ] x_n+[a_n>l_n] xn+[an>ln]

总时间复杂度 O ( n ) O\left(n\right) O(n)

源码

代码其实很短

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<LL,LL> pii;
#define MAXN 200005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
#define lowbit(x) (x&-x)
const int mo=998244353;
const int inv2=5e8+4;
const int jzm=2333;
const int zero=15;
const int INF=0x3f3f3f3f;
const double Pi=acos(-1.0);
const double eps=1e-9;
const int orG=3,ivG=332748118;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,a[MAXN];
struct range{int x,l,r;}dp[MAXN];
int main(){
    read(n);for(int i=1;i<=n;i++)read(a[i]);
    dp[0]=(range){0,0,0};
    for(int i=1;i<=n;i++){
        int l=dp[i-1].l,r=dp[i-1].r;
        if(a[i]>=a[i-1]-l)dp[i].x=dp[i-1].x,dp[i].l=min(min(l,a[i]-a[i-1]+l),a[i]),
            dp[i].r=min(max(max(l,a[i]-a[i-1]+l),min(r,a[i]-a[i-1]+r)),a[i]);
        else dp[i].x=dp[i-1].x+1,dp[i].r=a[i],
            dp[i].l=min(max(max(l,a[i]-a[i-1]+l),min(r,a[i]-a[i-1]+r)),a[i]);
    }
    printf("%d\n",dp[n].x+(a[n]>dp[n].l));
    return 0;
}

谢谢!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值