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

题目描述

题目地址【IN

给你nn个整数aia_i,保证1aim1\leq a_i\leq m
对于每一个1xm1\leq x\leq m,我们令

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

然后请求出x=1mwx\bigotimes\limits_{x=1}^mw_x
(异或和,也就是w1 xor w2 xor wmw_1\ xor\ w_2\cdots \ xor\ w_m

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

首先暴力就是nmnm的了,就不说了。


  • 正解

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

  • aix\sum\limits\lfloor\frac{a_i}{x}\rfloor
  • xai\sum\limits\lfloor\frac{x}{a_i}\rfloor

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

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

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

所以总复杂度为O(mlogm+nlogm+m)O(mlogm+nlogm+m)

丑陋QWQ代码,记得开long longlong\ 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;
}
posted @ 2018-10-22 17:49  VictoryCzt  阅读(79)  评论(0)    收藏  举报