【区间合并+贡献法】codeforces 1789 C. Serval and Toxel's Arrays
题目
https://codeforces.com/problemset/problem/1789/C
题意
第一行输入一个正整数 \(T(1 \leq T \leq 10^4)\),代表 \(T\) 组测试用例。
对于每组测试用例:第一行输入两个正整数 \(n, m(1 \leq n, m \leq 2 \times 10^5)\),分别代表要输入的数组长度和修改次数。第二行输入一个长度为 \(n\) 的数组 \(a(1 \leq a[i] \leq n + m)\)。接下来 \(m\) 行,每行输入两个整数 \(p, q\),代表将 \(a[p]\) 的值修改为 \(q\)。
保证初始数组,以及每次修改后的数组,都不包含重复元素,且对数组的修改是永久操作;\(n, m\) 的总和都不超过 \(2 \times 10^5\)。
经过 \(m\) 次修改,我们可以得到如下 \((m + 1)\) 个数组:
\(A_1 = a\)
\(A_2 = 第一次修改后的 a\)
...
\(A_{m+1} = 第 m 次修改后的 a\)
要求计算出这个 \((m + 1)\) 个数组两两数组进行组合,不同元素的数量之和。
题解
不妨假设有 \(m + 1\) 组数组,这 \(m + 1\) 组数组中元素 \(x\) 的出现次数为 \(u(1 \leq u \leq m + 1)\) 次,那么这个 \(x\) 对于答案的贡献会是多少呢?
分类讨论:
- 与含有元素 \(x\) 的其他一组数组进行组合,那么两两组合 \(x\) 只会贡献 \(1\) 次答案;若总的有 \(u\) 组数组具有元素 \(x\),则只会贡献 \((u \times (u - 1) >> 1)\) 次答案;
- 与不含有元素 \(x\) 的其他数组进行组合,不妨假设其他数组共有 \(v\) 组,含有元素 \(x\) 的数组共有 \(u\) 组,那么会贡献 \((u * v)\) 次答案,其中 \(u + v = m + 1\)。
综上所述,我们只需要维护出每个元素出现的区间(不妨用左闭右开区间),在最后进行区间合并,得到每个元素在全部数组中出现的次数,不妨假设次数为 \(u\),则其对答案的贡献即为:\((u \times (u - 1) >> 1) + u \times (m + 1 - u)\)。
参考代码
#include<bits/stdc++.h>
#define PII pair<int, int>
#define eb(x) emplace_back(x)
using namespace std;
typedef long long ll;
constexpr int N = 2e5 + 7;
int T, n, m, p, q;
int a[N];
void solve() {
ll ans = 0LL;
cin >> n >> m;
int w = m + 1;
vector<PII> v[n + m + 1];
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
v[a[i]].eb(PII(0, w));
}
for (int i = 1; i <= m; ++ i) {
cin >> p >> q;
v[a[p]].back().second = i;
v[q].eb(PII(i, w));
a[p] = q;
}
for (int i = 1; i <= n + m; ++ i) {
if (v[i].empty()) continue;
ll len = 0LL;
int l = v[i][0].first, r = v[i][0].second;
for (int j = 1; j < v[i].size(); ++ j) {
if (v[i][j].first == r) r = v[i][j].second;
else {
len += r - l;
l = v[i][j].first;
r = v[i][j].second;
}
}
len += r - l;
ans += (len * (len - 1) >> 1) + len * (w - len);
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> T;
while (T --) {
solve();
}
return 0;
}
浙公网安备 33010602011771号