题意: 给定一个长度为 的序列 ,每次可以选择 的区间将 都减 ,代价为 ,问在将整个序列都变为 的最小操作数下的最小代价和最大代价分别是多少。
题解: 先差分序列
,最小操作数一定是
。
对于
,若小于等于
,必然可以和
一起被处理;若大于
,那就需要多
次处理。
取最小值,即对于差分序列
依次找到距离每个大于
的
的最近的小于
的
,用队列维护即可。
取最大值,即对于差分序列
依次找到距离每个小于
的
的最近的大于
的
,用栈维护即可。
简单证明:
对于最小值,我们从左往右处理正数,那么对于每个正数,其选取的区间一定是最短的。
对于最大值,我们最左往右处理负数,因为负数必须被之前的正数处理掉。那么该负数越往左边的正数就越可能被后面的负数所处理,每个正数被处理的区间就会越长。
看官方题解还有线段树或分治的做法,太弱了确实理解不了。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 10;
const int mod = 1e9 + 7;
int a[N], b[N];
int n;
struct Node{
int v, id;
};
int jz(int x) {
return x < 0 ? -x : x;
}
Node stk[N]; int top;
Node q[N]; int hh, tt;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
//最小
a[0] = a[n + 1] = 0;
for(int i = 1; i <= n + 1; i++) b[i] = a[i] - a[i - 1];
ll Min = 0;
hh = 0, tt = -1;
for(int i = 1; i <= n + 1; i++) {
if(b[i] > 0) q[++tt] = {b[i], i};
else if(b[i] < 0) {
while(b[i] != 0) {
int flag = (jz(q[hh].v) >= jz(b[i]));
if(flag) {
Min = (Min + 1ll * (i - q[hh].id) * (i - q[hh].id) % mod * jz(b[i])) % mod;
q[hh].v -= jz(b[i]);
b[i] = 0;
if(q[hh].v == 0) hh++;
}
else {
Min = (Min + 1ll * (i - q[hh].id) * (i - q[hh].id) % mod * jz(q[hh].v)) % mod;
b[i] += q[hh].v;
hh++;
}
}
}
}
//最大
a[0] = a[n + 1] = 0;
for(int i = 1; i <= n + 1; i++) b[i] = a[i] - a[i - 1];
ll Max = 0;
top = 0;
for(int i = 1; i <= n + 1; i++) {
if(b[i] > 0) stk[++top] = {b[i], i};
else if(b[i] < 0) {
while(b[i] != 0) {
int flag = (jz(stk[top].v) >= jz(b[i]));
if(flag) {
Max = (Max + 1ll * (i - stk[top].id) * (i - stk[top].id) % mod * jz(b[i]) % mod) % mod;
stk[top].v -= jz(b[i]);
b[i] = 0;
if(stk[top].v == 0) top--;
}
else {
Max = (Max + 1ll * (i - stk[top].id) * (i - stk[top].id) % mod * jz(stk[top].v) % mod) % mod;
b[i] += stk[top].v;
top--;
}
}
}
}
printf("%lld %lld\n", Min, Max);
}