https://namomo.top:8081/contest/2

C. 序列

题意:给定一个数列,每次将一个区间 [l,r][l,r] 中所有数-1,代价 (rl+1)2(r-l+1)^2,要求最终全变为0,问在用最少次数操作的前提下,最小代价与最大代价。

差分

考虑逆向操作,从全为 0 的数列,每次选一个区间+1,形成给定数列。

每次选一个区间全部加 1 ,可以差分完成,所以求出给定数列的差分数组,要求操作次数最少,所以差分数组中负数的位置一定是所选操作区间右端点+1,正数位置一定是所选操作区间的左端点,为 0 的位置一定不会作为差分时操作的位置。

所以差分数组相当于一个括号序列,在正数位置只能放左括号,负数只能放右括号。

设差分数组中正数位置为 a,负数位置为 b。

则总的代价 (a[i]b[i])2=(a[i]2+b[i]2)2(a[i]b[i])\sum(a[i]-b[i])^2=\sum(a[i]^2+b[i]^2)-2\sum(a[i]*b[i])

前一个求和是常数,不用管,由排序不等式可知要使得后一个求和最小,必须要使得 a 和 b 逆序配对,即 a 递增 配 b 递减,要使得最大,必须要顺序配对。

所以要求总的代价最小,则要每个右括号匹配最前面的左括号,所以可以用队列存储左括号,每次遇到右括号就取队列最前面的。

而要总代价最大,则必须最后一个右括号匹配第一个左括号,倒数第 2 个右括号匹配正数第 2 个左括号,以此类推。又能发现这样匹配如果在已知括号序列必定合法的情况下,就相当于每个右括号匹配左边最靠近的左括号。所以可以用栈实现,每次右括号选栈顶的左括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int N = 5e5 + 10;
int n;
ll a[N], b[N], mx, mn;
typedef pair<ll, ll>pii;
queue<pii>q;
stack<pii>st;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)scanf("%lld", &a[i]);
for (int i = 1; i <= n + 1; i++)b[i] = a[i] - a[i - 1];
for (int i = 1; i <= n + 1; i++) {
if (b[i] > 0) {
q.push(pii(b[i], i));
st.push(pii(b[i], i));
}
else if (b[i] < 0) {
ll tmp = -b[i];
while (tmp > 0) {
ll cn = min(tmp,q.front().first), po = q.front().second;
if (q.front().first == cn)q.pop();
else q.front().first -= cn;
tmp -= cn;
mn = (mn + cn * (i - po) % mod*(i - po) % mod) % mod;
}

tmp = -b[i];
while (tmp > 0) {
ll cn = min(tmp, st.top().first), po = st.top().second;
if (st.top().first == cn)st.pop();
else st.top().first -= cn;
tmp -= cn;
mx = (mx + cn*(i - po) % mod*(i - po) % mod) % mod;
}
}
}
printf("%lld %lld\n", mn, mx);
return 0;
}