2024 CCPC Final Chengdu (第九届 CCPC 总决赛): A 题 – Add One 2 题解

本题解同步发布于我的个人博客

题意

题目链接

给定一个序列 y y y​, 初始有一个全零序列 x x x​。每次可以选择一个长度为 k k k​ 的前缀或者一个长度为 k k k​ 的后缀将其加一,代价是 k k k​。问最少需要多少代价能使得对于所有 i i i​ 都满足 b i ≥ a i b_i\ge a_i biai​。

序列的长度范围为 1 ≤ n ≤ 1 0 6 1 \le n \le 10^6 1n106

解答

Key 1: 考虑什么情况下,答案等于 ∑ i = 1 n y i \sum_{i=1}^{n} y_i i=1nyi

发现当 y 1 ≥ ∑ i = 1 n − 1 max ⁡ ( 0 , y i − y i + 1 ) y_1 \ge \sum_{i=1}^{n-1} \max(0,y_i-y_{i+1}) y1i=1n1max(0,yiyi+1) y n ≥ ∑ i = 1 n − 1 max ⁡ ( 0 , y i + 1 − y i ) y_n \ge \sum_{i=1}^{n-1} \max(0,y_{i+1}-y_i) yni=1n1max(0,yi+1yi)​ 时符合。

证明:如果对于每个 k k k 都有 y k ≥ ∑ i = k n − 1 max ⁡ ( 0 , y i − y i + 1 ) y_k \ge \sum_{i=k}^{n-1} \max(0,y_i-y_{i+1}) yki=kn1max(0,yiyi+1),那么对于任何一个 y i > y i + 1 y_i > y_{i+1} yi>yi+1 的情况,我们都可以令 y 1 ∼ y i y_1 \sim y_i y1yi 全部减去 y i − y i + 1 y_{i}-y_{i+1} yiyi+1 ,这样最终会形成一个 y 1 = y 2 = ⋯ = y k ≠ y k + 1 = ⋯ = y n y_1 = y_2 =\dots = y_k \neq y_k+1 = \dots =y_n y1=y2==yk=yk+1==yn 的形式,容易发现是符合的。然后你可以进行推导,如果 y k < ∑ i = k n − 1 max ⁡ ( 0 , y i − y i + 1 ) y_k < \sum_{i=k}^{n-1} \max(0,y_i-y_{i+1}) yk<i=kn1max(0,yiyi+1),则必然有 y 1 < ∑ i = k − 1 n − 1 max ⁡ ( 0 , y i − y i + 1 ) y_1 < \sum_{i=k-1}^{n-1} \max(0,y_i-y_{i+1}) y1<i=k1n1max(0,yiyi+1),因此也有 y 1 < ∑ i = 1 n − 1 max ⁡ ( 0 , y i − y i + 1 ) y_1 < \sum_{i=1}^{n-1} \max(0,y_i-y_{i+1}) y1<i=1n1max(0,yiyi+1),由逆否命题知,我们只需要 y 1 ≥ ∑ i = 1 n − 1 max ⁡ ( 0 , y i − y i + 1 ) y_1 \ge \sum_{i=1}^{n-1} \max(0,y_i-y_{i+1}) y1i=1n1max(0,yiyi+1) 即可。同理可知 y n ≥ ∑ i = 1 n − 1 max ⁡ ( 0 , y i + 1 − y i ) y_n \ge \sum_{i=1}^{n-1} \max(0,y_{i+1}-y_i) yni=1n1max(0,yi+1yi) 也成立。

由对称关系知,其中一者成立,另一者也成立,即 y 1 + y n ≥ ∑ i = 1 n − 1 ∣ y i − y i + 1 ∣ y_1+y_n \ge \sum_{i=1}^{n-1}|y_i-y_{i+1}| y1+yni=1n1yiyi+1 时,有答案等于 ∑ i = 1 n y i \sum_{i=1}^{n} y_i i=1nyi​。

对称关系怎么来的?首先,显然有 y 1 + ∑ i = 1 k − 1 ( y i + 1 − y i ) = y n y_1+\sum_{i=1}^{k-1}(y_{i+1}-y_{i})=y_n y1+i=1k1(yi+1yi)=yn,将求和式子中的负项留在左边,正向移到右边,则有 y 1 − ∑ i = 1 n − 1 max ⁡ ( 0 , y i − y i + 1 ) = y n − ∑ i = 1 n − 1 max ⁡ ( 0 , y i + 1 − y i ) y_1-\sum_{i=1}^{n-1} \max(0,y_i-y_{i+1})=y_n - \sum_{i=1}^{n-1} \max(0,y_{i+1}-y_i) y1i=1n1max(0,yiyi+1)=yni=1n1max(0,yi+1yi),故有上述两式等价。

故问题转化为,对于题目给定的 y i y_i yi 数列,我们可以令其中的某些 y i y_i yi 加上某些数,形成一个新的数列 x i x_i xi x i x_i xi 的和即为答案,那么我们要做的就是使这些加的数尽可能少。不妨记 M = y 1 + y n − s u m i = 1 n − 1 ∣ y i − y i + 1 ∣ M=y_1+y_n -sum_{i=1}^{n-1}|y_i-y_{i+1}| M=y1+ynsumi=1n1yiyi+1,我们要使得 M ≥ 0 M \ge 0 M0

我们发现,若数列中有若干个相同的数 y l = y l + 1 = ⋯ = y r y_l=y_{l+1} =\dots =y_r yl=yl+1==yr,如果有 y l − 1 ≥ y l y_{l-1} \ge y_l yl1yl y r + 1 ≥ y r = y l y_r+1 \ge y_r=y_l yr+1yr=yl,那么我们让 y l ∼ y r y_l \sim y_r ylyr 全部加上 1 1 1,就可以使得 M M M 加上 2 2 2,而这一操作的代价是 r − l + 1 r-l+1 rl+1。显然,我们希望找到尽可能短的 y l ∼ y r y_l \sim y_r ylyr​ 序列满足上述条件。

key 2:考虑怎么尽可能快而准确地找到当前数列中最短的合法序列。

我们以 y i y_i yi 为权值,建立一棵大根堆的笛卡尔树,可以发现,对于每一个点,对其操作时的长度就是以其为根的子树的大小,其可以加的最大数值就是其与其父亲节点的差值。然后,我们按照子树大小从小到大遍历所有点并更新 M M M 和答案,直至 M ≥ 0 M \ge 0 M0​ 时结束遍历。

由于子树大小不超过 n n n,可以用桶排做到 O ( n ) O(n) O(n) 的时间复杂度。也可直接 sort,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),足够通过。

代码

#include<bits/stdc++.h>
#define ll long long
#define pi pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define int long long
using namespace std;
const int N=1e6+5;
int ls[N],rs[N],fa[N];
int T,n,y[N];
struct node{
    int siz,pos;
}a[N];
void dfs(int u,int f){
    if(u==0) return;
    fa[u]=f;a[u].siz=1;
    dfs(ls[u],u);dfs(rs[u],u);
    a[u].siz+=a[ls[u]].siz;a[u].siz+=a[rs[u]].siz;
    a[u].pos=u;
}
bool cmp(node i,node j){
    return i.siz<j.siz;
}
void solve(){
    scanf("%lld",&n);
    ll ans=0,now=0,M=0;
    for(int i=1;i<=n;i++) fa[i]=ls[i]=rs[i]=0;
    for(int i=1;i<=n;i++){
        scanf("%lld",&y[i]);
        ans+=y[i];
        if(i!=1) now+=abs(y[i]-y[i-1]);
    }
    M=y[1]+y[n];
    stack<int> st;
    for(int i=1;i<=n;i++){
        while(!st.empty()&&y[i]>y[st.top()]){ls[i]=st.top();st.pop();}
        if(!st.empty()) rs[st.top()]=i;
        st.push(i);
    }
    int root=0;
    while(!st.empty()){root=st.top();st.pop();}
    dfs(root,0);
    sort(a+1,a+1+n,cmp);
    for(int u=1;u<=n&&M<now;u++){
        if((now-M)<=2*(y[fa[a[u].pos]]-y[a[u].pos])){
            ans+=(now-M)/2*a[u].siz;
            now-=(now-M);
        }
        else{
            now-=2*(y[fa[a[u].pos]]-y[a[u].pos]);
            ans+=(y[fa[a[u].pos]]-y[a[u].pos])*a[u].siz;    
        }
    }
    printf("%lld\n",ans);
}
signed main(){
    scanf("%lld",&T);
    while(T--) solve();
    return 0;
}
  • 38
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值