C. Serval and Toxel's Arrays
读完题后我们需要注意到一点:修改后保证每个数组内元素仍是不重复的。之后我们开始逐一拆解问题。每一次修改之后,拿当前的数组和之前出现过的数组相比较,统计当前数列中的元素在之前一共出现了多少次,每一个重复的值对结果的贡献是-1。引用一个别的博主举的例子:假设当前是第5轮修改,某一个a[i] = 5,并且前4轮的修改结果和初始数列中共有2个5,那么在本轮a[i] = 5对于答案的贡献就是 5 - 2 = 3(用本轮结果与前面的个结果比较时,只有3次的比较中产生了贡献) 。
如何求本次修改后的所有元素在之前数组出现了几次?假设所有元素都不同的值 - 重复次数。
约定:i为第i次修改
1.假设所有元素都不同的值:2*n*i
这条式子顾名思义就是取任意两数组,取了i次,根据下图很容易理解
2.但是仔细一想,不对啊,{原,a3}+{a1,a3}+{a2,a3},就算你【原数组、1、2、3】中每个数组的每个元素互不相同,其中的a3不是重复相加了吗,所以我们需要减去重复的数,重复的数怎么算?
(1)首先我们引入两个数组辅助记录:sum[i]:i在之前累计出现次数,last[i]:i上次出现在哪一个数组里,设置一个变量cnt,记录这一轮所有数在本轮已经之前累计出现的次数。
(2)让cnt加上 2*n*i(最大可能值)。先把这一轮被换掉的那一个数的多减的次数减去,然后加上替换后的数的重复次数,最后加上这一轮的所有数(也就是+n),得到的是这一轮所有数在本轮已经之前累计出现的次数。
Tips:
1.当一个数被修改时,可用当前位置与上一次位置相减得到重复出现次数,具体原因依然可以看上面的图理解(假设当前位置为5,上一次位置为1,那么一共会出现1,5 2,5 3,5 4,5四次取并集,因为1改后,2,3,4都保留着修改后的数)
#define int long long
const int N = 4e5+10;
int a[N];
void solve()
{
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
vector<int>last(n+m+1);//记录上一次出现位置
vector<int>sum(n+m+1);//sum[i]:数字i累计出现次数
for(int i = 1;i<=n;i++){
cin>>a[i];
last[a[i]] = 0;
}
int ans = 0;
int cnt = 0;这一轮所有数在本轮已经之前累计出现的次数
for(int i=1;i<=m;i++){
int l,r;
cin>>l>>r;
sum[a[l]] += i-last[a[l]];//当这个数被修改时,即可用当前位置与上一次位置相减得到一个重复出现次数
last[r] = i;
cnt += n;
cnt -= sum[a[l]];//减去多减的被替换的数字个数
cnt += sum[r];
ans += 2*n*i-cnt;//结果的最大可能值(2*n*i)减去重复的次数
a[l] = r; //修改数值
}
cout<<ans<<endl;
}
}
参考链接:https://zhuanlan.zhihu.com/p/609490320
同时,我碰到了个问题,long long和int相比哪一个运算效率更快?和运算数大小、编译器、操作系统这些有关,看哪天自己试验下吧
这道题用long long开数组比用int快了47ms,两次都开了cin cout优化