【20231009A】区分度题面&&题解

 

题面

题意概括

定义数列 a,满足 ak 为:

对于所有 1ik,不同的 ki 的个数。

现希望求出 kr(mod=m)1knak 的值

数据范围:1t100r<mn1013

题解

首先,这道题既然可以达到 1013 数量级,那么任何 O(tn) 级的算法肯定都是过不去的。

那我们来看看 a 这个数列。从题面中给出的前几项我们就可以猜出绝对有规律,那么打表猜一下规律:

#include<bits/stdc++.h>
using namespace std;
int main()
{
	for(int i=1;i<=100;i++)
	{
		int ans=1;
		for(int j=2;j<=i;j++)
		{
			if(i/j!=i/(j-1)) ans++;
		}
		cout<<ans<<" ";
	}
}

打出来的表是这样的:

1,2,2,3,3,4,4,4,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,9,9,9,9,9,10,10,10,10,10,10,11,11,11,11,11,11

相信已经可以看出来规律了。(证明略,请自行查阅关于数列分块相关论述)

用数学语言描述一下,就是对于 p 最大的 ap=i,有:

i 为单数时,p=(j=2i+122j)+1=(i+12)2+i+121 ,且可以发现 i+12=count(ap=i)

i 为双数时同理,p=(j=2i22j)+1+i+22=(i+22)21,且可以发现 i+22=count(ap=i)

综上,i+22=count(ap=i) 。令 a=count(ap=i)

根据刚才的结论,我们可以得出:

i 为双数时,有:

(a1)2+(a1)p<a2

解方程 (a1)2+(a1)=p,得 a1=4p+112 (负根舍去)

a1=4p+112,显然有 2(a1)4p+112a1

2a14p+12a,又由于 p<a2 ,则 pa21,则 4p+1<4a24p+1<2a

2a1=4p+1,为单数,则 4p+112=4p+112

i=2a2=2(a1) ,所以 i=4p+11

i 为单数时,同理有 i=4p+11,证明方法与上面类似。

ap=4p+11 (代码中的 topoint 函数)

接下来就简单了。我们枚举每一个 ap 的不同的值 i,并求出到底有多少个 ap 需要我们累加到答案中(即下面代码里面的 cnt )即可。为了求出 cnt ,我们可以求出上一个被加入答案的地方 last(它可以从上一个枚举的 i 那里继承,第一个 last=r),并求出这一段(定义 ap 值相同的为一段)的右端 rl,则有:

cnt=((rllast)/m+1)

好了,现在万事俱备,但还是有两个点要注意:

1.极限数据(如n=1013,m=1,r=0)会爆long longunsigned long long,使用__int128可以解决

2.由于 c++ 自带的sqrt()常数很大,多次调用可能超时,所以我们要尽量不要再循环里面使用( 你猜我考场上怎么挂分的 )。代码中有注释。

#include<bits/stdc++.h>
using namespace std;
inline long long read()//快读函数,可以快速读取(输入) 
{
	long long s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
	return s*w;
}
long long topoint(long long a)
{
	long long res=(long long)floor(sqrt(a*4+1))-1;
	return res;
}
void write(__int128 x)
{
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int t;
int main()
{
	//freopen("discrimination.in","r",stdin);
	//freopen("discrimination.out","w",stdout);
	scanf("%d",&t);
	while(t--)
	{
		long long n,m,r;
		__int128 ans=0;
		n=read();
		m=read();
		r=read();
		if(!r) r+=m;
		long long last=r;
		for(long long i=topoint(r);i<=topoint(n)&&last<=n;i++)
		{
			while((i+2)*(i+2)<=4*last+1) i++;
			//若42行改成i=topoint(last);则仍然正确,因为两者是等价的,但会TLE(80pts)
			bool flag=(i%2==1);
			long long a=(i+2)/2,rl=min(n,flag==1?a*a+a-1:a*a-1);
			long long cnt=((rl-last)/m+1);
			ans+=cnt*i;
			last+=cnt*m;
		}
		write(ans);
		printf("\n");
	}
}