Codeforces Round 853 (Div. 2) C题

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优化

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值