P2261 [CQOI2007]余数求和——解题报告

一、题目链接

P2261 [CQOI2007]余数求和

二、题目大意

给定正整数 n n n k k k,要求计算 ∑ i = 1 n k   m o d   i \sum_{i=1}^{n}k\ mod\ i i=1nk mod i
数据范围: 1 ≤ n , k ≤ 1 0 9 1\leq n,k\leq 10^9 1n,k109

三、题目分析

如果直接求解这个问题,它的时间复杂度就是 O ( n ) O(n) O(n),不满足题目的要求,所以我们要首先分析这个运算的本质。

根据取余运算的特性,我们知道 k   m o d   i = k − ⌊ k / i ⌋ × i k\ mod\ i=k-\lfloor k/i\rfloor \times i k mod i=kk/i×i,所以,我们要求的东西就变成了 n × k − ∑ i = 1 n ⌊ k / i ⌋ × i n\times k-\sum_{i=1}^{n}\lfloor k/i\rfloor \times i n×ki=1nk/i×i

我们很容易发现,在当 i i i为某些值的时候, ⌊ k / i ⌋ \lfloor k/i\rfloor k/i的值是相同的,我们考虑如何将这些相同的数合在一起计算,降低时间复杂度。

  • 我们设 t e m p = ⌊ k / ⌊ k / x ⌋ ⌋ temp=\lfloor k/ \lfloor k/x\rfloor\rfloor temp=k/k/x⌋⌋ t e m p ≥ x temp\geq x tempx,所以 ⌊ k / t e m p ⌋ ≤ ⌊ k / x ⌋ \lfloor k/temp\rfloor \leq \lfloor k/x\rfloor k/tempk/x
  • 又因为 t e m p ≤ k / ⌊ k / x ⌋ = a n o temp\leq k/\lfloor k/x\rfloor=ano tempk/k/x=ano,所以 ⌊ k / t e m p ⌋ ≥ ⌊ k / a n o ⌋ \lfloor k/temp\rfloor \geq \lfloor k/ano\rfloor k/tempk/ano
  • 显然, k / a n o = ⌊ k / x ⌋ k/ano=\lfloor k/x\rfloor k/ano=k/x。即 ⌊ k / t e m p ⌋ ≥ ⌊ k / x ⌋ \lfloor k/temp\rfloor \geq \lfloor k/x\rfloor k/tempk/x

所以我们知道 ⌊ k / t e m p ⌋ = ⌊ k / x ⌋ \lfloor k/temp\rfloor = \lfloor k/x\rfloor k/temp=k/x。即在 i ∈ [ x , ⌊ k / ⌊ k / x ⌋ ⌋ ] i\in[x,\lfloor k/ \lfloor k/x\rfloor\rfloor] i[x,k/k/x⌋⌋] ⌊ k / i ⌋ \lfloor k/i\rfloor k/i相等。

  • 于是我们发现,在同一个区间,即 ⌊ k / x ⌋ \lfloor k/x\rfloor k/x不变的时候。当 i i i变化时, ⌊ k / x ⌋ ∗ i \lfloor k/x\rfloor*i k/xi就形成了一个等差数列,我们可以直接用等差数列求和公式来计算这一段的答案,时间复杂度为 O ( 1 ) O(1) O(1)
  • 接下来考虑有多少个区间需要计算
    • i < k i<\sqrt{k} i<k 时,因为除数只有i种的限制,显然结果最多只有 k \sqrt{k} k 种。
    • i > k i>\sqrt{k} i>k 时, ⌊ k / i ⌋ < k \lfloor k/i\rfloor < \sqrt{k} k/i<k 显然成立,所以也只有 k \sqrt{k} k 种。

综上所述,区间个数的数量级在 k \sqrt{k} k ,所以时间复杂度为 O ( k ) O(\sqrt{k}) O(k )

四、正解程序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>

using namespace std;
typedef long long ll;

int main()
{
	ll n,k,ans,gx;
	scanf("%lld%lld",&n,&k);
	ans=n*k;
	if(k==0)
	{
		printf("%lld\n",ans);
		return 0;
	}
	for(ll x=1;x<=n;x=gx+1)
	{
		gx=k/x?min(k/(k/x),n):n;
		ans-=(k/x)*(x+gx)*(gx-x+1)/2;
	}
	printf("%lld\n",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值