[Harbour.Space Scholarship Contest 2021-2022 (open for everyone, rated, Div. 1 + Div. 2)](https://codeforces.ml/contest/1553)

Harbour.Space Scholarship Contest 2021-2022 (open for everyone, rated, Div. 1 + Div. 2)

F. Pairwise Modulo

You have an array a consisting of n distinct positive integers, numbered from 1 to n. Define pk as

\[p_k=\sum_{1\le i,j\le k}a_i\ mod\ a_j \]

You have to find and print p1,p2,…,pnp1,p2,…,pn.

\[2\le n \le 2*10^5,1\le a_i \le3*10^5,a_i\neq a_j,if\ i\neq\ j\\ \]

时限4s

\[首先考虑去拆a_i\ mod\ a_j\\ a_i\ mod\ a_j=a_i-\lfloor\frac{a_i}{a_j}\rfloor a_j\\ 设sum_i为a_i的前缀和,ans_i为选了前i个的贡献 \\我们每加入一个新的数a_i会产生\sum_{j=1}^{i-1}(a_i-\lfloor\frac{a_i}{a_j}\rfloor a_j) +\sum_{j=1}^{i-1}(a_j-\lfloor\frac{a_j}{a_i}\rfloor a_i)的贡献\\ 对于后一部分只需分别统计在区间[0,a_i),[a_i,2a_i)\cdots中已被加入的数的个数\\ 设上界为M那么每个数至多会统计\frac{M}{a_i}个区间,每个a_i都不相等,那么这里最坏情况为\\ \frac{M}{1}+\frac{M}{2}+\frac{M}{3}+\frac{M}{4}+\cdots\approx MlnM\\ 区段内数的个数可以线段树或树状数组维护单次修改与查询复杂度O(logn)\\ 后一部分总体复杂度O(MlnMlog M)\\ \]

\[对前一部分,先提供一种假的做法,对一个新加入的b_i我们只用考虑\frac{b_i}{k}有多少种可能的结果\\ 由数论分块可以求出有2\sqrt{b_i}种不同的结果,分别对这些区间内的数求和加到贡献中\\ 区间内数的求和可以线段树或树状数组维护总体复杂度O(2M\sqrt{M}logM)\\ 看起来4s的时限似乎能过,但实际上在我本地跑了6900ms,可能没有那个2的常数就能过\\ 之后一直在尝试各种优化,但还是没卡过去(被4s的时限骗了,以为这个复杂度是对的) 放一下假的代码 \]

#include<bits/stdc++.h>
using namespace std;
const int maxn=300005;
//#define int long long
#define lowbit(x) x&(-x)
long long a1[500050];//树状数组
int a[500050];
int b[500100];
long long sum[500100];
long long ans[500000];
inline int read()
{
	int res=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9')
	{
		res=(res<<1)+(res<<3)+c-'0';
		c=getchar();
	}
	return res;
}
void add(int x)
{
	while(x<=maxn)
	{
		a[x]++;
		x+=x&(-x);
	}
} 
int get(int x)
{
	if(x<=0)return 0;
	int ans=0;
	while(x)
	{
		ans+=a[x];
		x-=x&(-x);
	}
	return ans;
}
void add1(int x,int zhi)
{
	while(x<=maxn)
	{
		a1[x]+=zhi;
		x+=x&(-x);
	}
} 
long long get1(int x)
{
	if(x<=0)return 0;
	long long ans=0;
	while(x)
	{
		ans+=a1[x];
		x-=x&(-x);
	}
	return ans;
}
void print(long long x)
{
	if(x>=10)print(x/10);
	putchar(x%10+'0');
}
signed main()
{
	int ks=clock();
//	freopen("std.in","r",stdin);
//	freopen("std.out","w",stdout);
	int n;
	scanf("%d",&n);
	int jx=0;
	int timel=0;
	for(int i=1;i<=n;i++)
	{
		b[i]=read();
		sum[i]=sum[i-1]+b[i];
		jx=max(jx,b[i]);
		add(b[i]);
		add1(b[i],b[i]);
		int cnt=0;
		int now=0;
		for(int l=0,r;l<=jx;l=r+1)
		{
			if(now==i)break;
			r=l+b[i]-1;
			if(r>maxn)r=maxn;
			int ans1=get(r)-get(l-1);
			now+=ans1;
//			timel++;
//			cout<<l<<" "<<r<<" "<<get(l-1)<<" "<<get(r)<<"\n";
			ans[i]-=1ll*b[i]*cnt*ans1;
			cnt++;
		}
		now=0;
		int limit=get1(b[i]-1);
		int cs=0;
		for(int l=1,r;l<=b[i];l=r+1)
		{
			if(now==limit)
			{
				cs=1;break;
			}
			r=b[i]/(b[i]/l);
			int ans1=get1(r)-get1(l-1);
			now+=ans1;
			timel++;
//			cout<<b[i]<<" "<<l<<" "<<r<<" "<<ans1<<"\n";
			ans[i]-=1ll*b[i]/l*ans1;
		}
		ans[i]+=ans[i-1];
		ans[i]+=sum[i-1];
		ans[i]+=1ll*b[i]*i;
		if(!cs)ans[i]+=b[i];
	}
//	for(int i=1;i<=n;i++)
//	cout<<b[i]<<" ";cout<<"\n";
//	for(int i=1;i<=n;i++)
//	cout<<sum[i]<<" ";cout<<"\n";
	for(int i=1;i<n;i++)
	{
//		printf("%lld ",ans[i]);
		print(ans[i]);putchar(' ');
	}
//	printf("%lld\n",ans[n]);
	print(ans[n]);putchar('\n');
	int js=clock();
//	cout<<ks-js<<"\n";
//	cout<<timel<<"\n";
	return 0;
}

\[之后看了官方题解,发现他对前一部分的处理是这样的\\ 不考虑用a_i去找a_j,而是在前面加入a_j时就考虑对后面的贡献\\ 如a_j加入后对区间[0,a_j)[a_j,2a_j)[2a_j,3a_j)\cdots的贡献分别为0,a_j,2a_j\cdots\\ 和后一部分的方法类似没加入一个数后,将a_i,2a_i\cdots的位置加上a_i,这样在统计前缀和时对应区间的贡献就是ka_i\\ 这样在新的a_i处的贡献可以直接求a_i的前缀和去解决\\ 复杂度和前一部分类似都是调和级数求和加上区间求和的复杂度O(logn),总体复杂度O(Mlog^2M)\\ 最终在cf上只跑了358ms,远低于4000ms\\ 粘一下代码 \]

#include<bits/stdc++.h>
using namespace std;
const int maxn=300005;
//#define int long long
#define lowbit(x) x&(-x)
long long a1[500050];//树状数组
int a[500050];
int b[500100];
long long sum[500100];
long long ans[500000];
inline int read()
{
	int res=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9')
	{
		res=(res<<1)+(res<<3)+c-'0';
		c=getchar();
	}
	return res;
}
void add(int x)
{
	while(x<=maxn)
	{
		a[x]++;
		x+=x&(-x);
	}
} 
int get(int x)
{
	if(x<=0)return 0;
	int ans=0;
	while(x)
	{
		ans+=a[x];
		x-=x&(-x);
	}
	return ans;
}
void add1(int x,int zhi)
{
	while(x<=maxn)
	{
		a1[x]+=zhi;
		x+=x&(-x);
	}
} 
long long get1(int x)
{
	if(x<=0)return 0;
	long long ans=0;
	while(x)
	{
		ans+=a1[x];
		x-=x&(-x);
	}
	return ans;
}
void print(long long x)
{
	if(x>=10)print(x/10);
	putchar(x%10+'0');
}
signed main()
{
	int ks=clock();
//	freopen("std.in","r",stdin);
//	freopen("std.out","w",stdout);
	int n;
	scanf("%d",&n);
	int jx=0;
	int timel=0;
	for(int i=1;i<=n;i++)
	{
		b[i]=read();
		sum[i]=sum[i-1]+b[i];
		jx=max(jx,b[i]);
		add(b[i]);
		for(int k=1;k*b[i]<=maxn;k++)
		add1(k*b[i],b[i]);
		int cnt=0;
		int now=0;
		for(int l=0,r;l<=jx;l=r+1)
		{
			if(now==i)break;
			r=l+b[i]-1;
			if(r>maxn)r=maxn;
			int ans1=get(r)-get(l-1);
			now+=ans1;
//			timel++;
//			cout<<l<<" "<<r<<" "<<get(l-1)<<" "<<get(r)<<"\n";
			ans[i]-=1ll*b[i]*cnt*ans1;
			cnt++;
		}
		now=0;
		int cs=0;
		int o=0;
		ans[i]-=get1(b[i]);
//		for(int l=1,r;l<=b[i];l=r+1)
//		{
//			if(now==limit)
//			{
//				cs=1;break;
//			}
//			r=b[i]/(b[i]/l);
//			int ans1=get1(r)-get1(l-1);
//			now+=ans1;
//			o++;
//			
//			timel++;
////			cout<<b[i]<<" "<<l<<" "<<r<<" "<<ans1<<"\n";
//			ans[i]-=1ll*b[i]/l*ans1;
//		}printf("%d %d\n",b[i],o);
		ans[i]+=ans[i-1];
		ans[i]+=sum[i-1];
		ans[i]+=1ll*b[i]*i;
		if(!cs)ans[i]+=b[i];
	}
//	for(int i=1;i<=n;i++)
//	cout<<b[i]<<" ";cout<<"\n";
//	for(int i=1;i<=n;i++)
//	cout<<sum[i]<<" ";cout<<"\n";
	for(int i=1;i<n;i++)
	{
		printf("%lld ",ans[i]);
//		print(ans[i]);putchar(' ');
	}
	printf("%lld\n",ans[n]);
//	print(ans[n]);putchar('\n');
//	int js=clock();
//	cout<<ks-js<<"\n";
//	cout<<timel<<"\n";
	return 0;
}
posted @ 2021-07-23 11:43  1427314831a  阅读(114)  评论(0编辑  收藏  举报