本题解同步发布于我的个人博客。
题意
题目链接。
给定一个序列 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 bi≥ai。
序列的长度范围为 1 ≤ n ≤ 1 0 6 1 \le n \le 10^6 1≤n≤106。
解答
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}) y1≥∑i=1n−1max(0,yi−yi+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) yn≥∑i=1n−1max(0,yi+1−yi) 时符合。
证明:如果对于每个 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}) yk≥∑i=kn−1max(0,yi−yi+1),那么对于任何一个 y i > y i + 1 y_i > y_{i+1} yi>yi+1 的情况,我们都可以令 y 1 ∼ y i y_1 \sim y_i y1∼yi 全部减去 y i − y i + 1 y_{i}-y_{i+1} yi−yi+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=kn−1max(0,yi−yi+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=k−1n−1max(0,yi−yi+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=1n−1max(0,yi−yi+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}) y1≥∑i=1n−1max(0,yi−yi+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) yn≥∑i=1n−1max(0,yi+1−yi) 也成立。
由对称关系知,其中一者成立,另一者也成立,即 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+yn≥∑i=1n−1∣yi−yi+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=1k−1(yi+1−yi)=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) y1−∑i=1n−1max(0,yi−yi+1)=yn−∑i=1n−1max(0,yi+1−yi),故有上述两式等价。
故问题转化为,对于题目给定的 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+yn−sumi=1n−1∣yi−yi+1∣,我们要使得 M ≥ 0 M \ge 0 M≥0。
我们发现,若数列中有若干个相同的数 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 yl−1≥yl 且 y r + 1 ≥ y r = y l y_r+1 \ge y_r=y_l yr+1≥yr=yl,那么我们让 y l ∼ y r y_l \sim y_r yl∼yr 全部加上 1 1 1,就可以使得 M M M 加上 2 2 2,而这一操作的代价是 r − l + 1 r-l+1 r−l+1。显然,我们希望找到尽可能短的 y l ∼ y r y_l \sim y_r yl∼yr 序列满足上述条件。
key 2:考虑怎么尽可能快而准确地找到当前数列中最短的合法序列。
我们以 y i y_i yi 为权值,建立一棵大根堆的笛卡尔树,可以发现,对于每一个点,对其操作时的长度就是以其为根的子树的大小,其可以加的最大数值就是其与其父亲节点的差值。然后,我们按照子树大小从小到大遍历所有点并更新 M M M 和答案,直至 M ≥ 0 M \ge 0 M≥0 时结束遍历。
由于子树大小不超过 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;
}