题目描述
难度分:1500
输入T(≤104)表示T组数据。所有数据的n之和≤2×105,m之和≤2×105。
每组数据输入n(1≤n≤2×105),m(1≤m≤2×105)和长为n的数组a(1≤a[i]≤n+m),下标从1开始。
然后输入m个修改操作,每个操作输入p(1≤p≤n)和v(1≤v≤n+m),表示把a[p]改成v。修改操作是永久的。
保证初始数组,以及每次修改后的数组,都不包含重复元素。
定义如下m+1个数组:
A0=a。
A1=第一次修改后的数组a。
A2=第二次修改后的数组a。
Am=第m次修改后的数组a。
定义f(i,j)为Ai+Aj(这是一个长为2n的数组)中的不同元素个数。输出所有f(i,j)之和,其中0≤i<j≤m。
输入样例
3
3 2
1 2 3
1 4
2 5
1 1
1
1 1
10 10
4 6 9 12 16 20 2 10 19 7
1 3
5 4
2 17
2 18
6 11
7 1
8 17
5 5
5 5
2 2
输出样例
13
1
705
算法
贡献法
答案ans首先一定会包括一部分n×(m+1)×m2,这是每个Ai+Aj前一半n个不同的数对答案做出的贡献,A0会跟A1到Am产生m个n,A1会跟A2到Am产生m−1个n的贡献……Am−1会跟Am产生1个n的贡献,高斯求和就能得到。接下来考虑后一半的贡献,对于某一个版本i的p索引位置数值v=Ai[p],它会跟所有在p位置≠v的Aj[p]产生1贡献,因此我们只需要知道每个数值val在[0,m]中几个版本的数组中出现过,存入在频数表counter中。
对于一个位置p,我们记录下它的值被修改的关键点(如果没有修改不算关键点),存在pos2pir[p]中,pos2pir[p]是个二元组数组,存的二元组是(这个值是在哪个版本的Ai出现的,被修改成的值)。然后遍历pos2pir中的所有位置i∈[1,n],对于固定的i,遍历二元组列表pos2pir[i],pos2pir[i][j].second的出现次数应该是pos2pir[i][j+1].first−pos2pir[i][j].first,即从pos2pir[i][j].first开始一直到下一个不一样的值出现的版本pos2pir[i][j+1].first之前,将其累加到counter[pos2pir[i][j].second]上。
最后遍历一下counter表,对于一个数值k,如果它出现了v次,那么就有m+1−v个数组是没有它的,将这两部分交叉组合,可以为答案贡献(m+1−v)×v。所以后一半对答案的贡献就是Σk(m+1−counter[k])×counter[k]2,全部累加到ans上就能得到最终答案。
复杂度分析
时间复杂度
预处理出pos2pir的时间复杂度为O(n+m)。遍历pos2pir计算答案需要遍历n个索引位置中的O(m)个关键点,所以时间复杂度还是O(n+m)。整个算法的时间复杂度为O(n+m)。
空间复杂度
空间消耗主要是pos2pir数组,其中存了O(m)个关键点,一共开了O(n)个索引的二元组列表,所以空间复杂度为O(n+m)。以及各个数值的频数统计表counter,在最差情况下要存O(n+m)个数值的频数,空间复杂度还是O(n+m)。因此,整个算法的额外空间复杂度为O(n+m)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 200010;
int T, n, m, a[N];
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
vector<PII> pos2pir[n + 1];
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
pos2pir[i].push_back({0, a[i]});
}
for(int i = 1; i <= m; i++) {
int p, v;
scanf("%d%d", &p, &v);
if(a[p] != v) {
a[p] = v;
pos2pir[p].push_back({i, a[p]});
}
}
LL ans = n * (1LL + m) * m / 2, delta = 0;
unordered_map<int, int, custom_hash> counter;
for(int i = 1; i <= n; i++) {
int len = pos2pir[i].size();
for(int j = 0; j < len; j++) {
int start = pos2pir[i][j].first, val = pos2pir[i][j].second;
if(j < len - 1) {
int end = pos2pir[i][j + 1].first;
counter[val] += end - start;
}else {
counter[val] += m + 1 - start;
}
}
}
for(auto&[k, v]: counter) {
delta += 1LL * (m + 1 - v) * v;
}
ans += delta>>1;
printf("%lld\n", ans);
}
return 0;
}