牛客网比赛211F题算式子题解

题目描述

题目地址【IN

给你 n n n个整数 a i a_i ai,保证 1 ≤ a i ≤ m 1\leq a_i\leq m 1aim
对于每一个 1 ≤ x ≤ m 1\leq x\leq m 1xm,我们令

w x = ∑ i = 1 n ( ⌊ a i x ⌋ + ⌊ x a i ⌋ ) w_x=\sum\limits_{i=1}^n\left(\lfloor\frac{a_i}{x}\rfloor+\lfloor\frac{x}{a_i}\rfloor\right) wx=i=1n(xai+aix)

然后请求出 ⨂ x = 1 m w x \bigotimes\limits_{x=1}^mw_x x=1mwx
(异或和,也就是 w 1   x o r   w 2 ⋯   x o r   w m w_1\ xor\ w_2\cdots \ xor\ w_m w1 xor w2 xor wm

  • 数据范围: 1 ≤ n , m ≤ 2 × 1 0 6 1\leq n,m\leq 2\times 10^6 1n,m2×106

首先暴力就是 n m nm nm的了,就不说了。


  • 正解

那么我们发现,对于原来的每个 w x w_x wx的式子,我们可以拆成两部分来看:

  • ∑ ⌊ a i x ⌋ \sum\limits\lfloor\frac{a_i}{x}\rfloor xai
  • ∑ ⌊ x a i ⌋ \sum\limits\lfloor\frac{x}{a_i}\rfloor aix

对于第一个式子,我们可以通过枚举 x x x,来算贡献,我们发现对于每一个 x x x,只有当跨过它的倍数时 ⌊ a i x ⌋ \lfloor\frac{a_i}{x}\rfloor xai才会变,又因为 a i a_i ai值范围较小,所以我们令 b i b_i bi表示 a a a序列中权值在 1 ∼ i 1\sim i 1i的个数和,那么枚举倍数,每一个 k x kx kx的贡献为 ( b k x + x − 1 − b k x − 1 ) × k (b_{kx+x-1}-b_{kx-1})\times k (bkx+x1bkx1)×k,所以第一个式子通过枚举 x x x及其倍数求出,复杂度为调和级数 O ( m l o g m ) O(mlogm) O(mlogm)

对于后面一个式子,我们反着枚举,枚举 a i a_i ai的倍数,所以先离散化,记录每个 a i a_i ai出现多少次,然后我们同样对于枚举的一个 k a i ka_i kai,它的贡献为 k × c n t [ a i ] k\times cnt[a_i] k×cnt[ai] c n t [ a i ] cnt[a_i] cnt[ai]表示 a i a_i ai的个数,这里必须离散化,否则多个 a i = 1 a_i=1 ai=1可以将复杂度卡到 n m nm nm
但是上面这个是有问题的,有的非 a i a_i ai的整倍数的贡献没有统计上,所以我们换个思路。

我们发现对于一个较小的 x x x,如果 ⌊ x a i ⌋ \lfloor\frac{x}{a_i}\rfloor aix有贡献,那么对于大的 x x x肯定也有贡献,所以我们计算的时候,只算当前的贡献,然后每个 x x x的总贡献通过前缀和即可算出,复杂度仍旧为 O ( n l o g m ) O(nlogm) O(nlogm)

所以总复杂度为 O ( m l o g m + n l o g m + m ) O(mlogm+nlogm+m) O(mlogm+nlogm+m)

丑陋QWQ代码,记得开 l o n g   l o n g long\ long long long

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=4e6+10;
ll n,m,A[M],B[M];
ll pref1[M],pref2[M],ans,cnt[M];
int main(){
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++)scanf("%lld",&A[i]),++B[A[i]],++cnt[A[i]];
	for(ll i=1;i<=m+m;i++)B[i]+=B[i-1];
	for(ll i=1;i<=m;i++){
		for(ll j=i,tc=1;j<=m;j+=i,++tc)
			pref1[i]+=(B[j+i-1]-B[j-1])*tc;
	}
	sort(A+1,A+n+1);
	int up=unique(A+1,A+n+1)-A-1;
	for(ll i=1;i<=up;i++){
		for(ll j=A[i];j<=m;j+=A[i])pref2[j]+=cnt[A[i]];
	}//注意:这里不用乘以它的贡献(cnt[A[i]]*(j/A[i])是因为小的肯定会对大的做出贡献,所以我们再求一次前缀和就好了。
	//相当于加上了贡献,因为对于j小的pref2[j],它的贡献是会累计到后面的 
	for(ll i=1;i<=m;i++)pref2[i]+=pref2[i-1];
	for(ll i=1;i<=m;i++){
		ans^=(pref1[i]+pref2[i]);
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值