题目描述
题目地址【IN】
给你
n
n
n个整数
a
i
a_i
ai,保证
1
≤
a
i
≤
m
1\leq a_i\leq m
1≤ai≤m。
对于每一个
1
≤
x
≤
m
1\leq x\leq m
1≤x≤m,我们令
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=1∑n(⌊xai⌋+⌊aix⌋)
然后请求出
⨂
x
=
1
m
w
x
\bigotimes\limits_{x=1}^mw_x
x=1⨂mwx
(异或和,也就是
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 1≤n,m≤2×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 1∼i的个数和,那么枚举倍数,每一个 k x kx kx的贡献为 ( b k x + x − 1 − b k x − 1 ) × k (b_{kx+x-1}-b_{kx-1})\times k (bkx+x−1−bkx−1)×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;
}