题解:AT_abc390_f [ABC390F] Double Sum 3
题面中进行的三个操作相当于:
- 将集合 \(S=\{a_i\mid L\le i\le R\}\) 划分为尽量少的集合的并集,其中每个集合包含一段连续整数。
注意到,一个连续整数段结束的标志是 \(x\in S\) 且 \((x+1)\notin S\)。这意味着,无论 \(x\) 属于什么连续整数段,它的后继 \(y\) 一定属于一个新的段。因此,我们只需要统计每个整数 \(x\) 对多少个区间有贡献即可,即对于每个整数 \(x\),统计有多少个区间中有 \(x\) 而没有 \(x+1\)。
但这样依然不是很好统计,因为一个区间可能包含多个 \(x\),容易数重或数漏。我们规定,每个符合要求的区间在其中包含的第一个 \(x\) 处被统计。
设 \(pre_i\) 为最大的 \(j<i\) 使得 \(A_j=x\) 或 \(A_j=x+1\),\(nxt_i\) 为最小的 \(j>i\) 使得 \(A_j=x+1\)。那么,任取 \(pre_i < L\le i\) 和 \(i\le R < nxt_i\),都满足 \(A_i\) 是区间 \([L,R]\) 包含的第一个 \(x\),且区间 \([L,R]\) 不包含 \(x+1\)。这样的区间共有 \((i-pre_i)(nxt_i-i)\) 个。
将每个 \(A_i\) 处的区间个数相加,答案即为 \(\sum\limits_{i=1}^n(i-pre_i)(nxt_i-i)\)。
核心代码:
#define rep(x, y, z) for(ll x = (y); x <= (z); ++x)
#define per(x, y, z) for(ll x = (y); x >= (z); --x)
ll n, a[N], pos[N], pre[N], nxt[N], ans;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
rep(i, 1, n) cin >> a[i];
rep(i, 1, n + 1) pos[i] = 0;
rep(i, 1, n) {
pre[i] = max(pos[a[i]], pos[a[i] + 1]);
pos[a[i]] = i;
}
rep(i, 1, n + 1) pos[i] = n + 1;
per(i, n, 1) {
nxt[i] = pos[a[i] + 1];
pos[a[i]] = i;
}
rep(i, 1, n) ans += (nxt[i] - i) * (i - pre[i]);
cout << ans << endl;
return 0;
}

浙公网安备 33010602011771号