洛谷 P2261 [CQOI2007]余数求和 ||整除(数论)分块

参考:题解

令f(i)=k%i,[p]表示不大于p的最大整数
f(i)=k%i=k-[k/i]*i
令q=[k/i]
f(i)=k-qi
如果k/(i+1)=k/i=q
f(i+1)=k-q(i+1)=k-qi-q=f(i)-q
于是,对于区间[l,r],使其之内任意两个整数i,j,都满足k/i=k/j,
则f(l)到f(r)是一个递减的等差数列,公差为[k/i]。
现在就是要把1到n分成这样的一些区间,设某个区间的商(公差)为p
设区间内某数为x,则现在要做的是解方程[k/x]=p
显然px<=k,因此x<=k/p,得出这个区间的最大的x也就是r=[k/p],
显然,l=[k/(p+1)]+1
数的个数为r-l+1

 1 #include<cstdio>
 2 typedef long long LL;
 3 LL ans,n,k;
 4 LL min(LL a,LL b)
 5 {
 6     return a>b?b:a;
 7 }
 8 int main()
 9 {
10     LL r;
11     scanf("%lld%lld",&n,&k);
12 //    LL minp=k/n,p;
13 //    LL maxp=min(n,k);
14 //    for(p=minp;p<=maxp;p++)
15 //    {
16 //        l=k/(p+1)+1;
17 //        if(p==0)
18 //            r=n;
19 //        else
20 //            r=min(n,k/p);
21 //        if(l<=r)
22 //        {
23 //            ans+=(r-l+1)*(k%l+k%r)/2;
24 //        }
25 //    }//太慢了,时间复杂度还是接近O(n),有大量多余循环
26     LL p,q;
27     for(int i=1;i<=n;i++)
28     {
29         //此处能循环到的i就是上面方法的l
30         p=k/i;
31         q=k%i;
32         //从i开始的区间,r就是min(n,[k/(k/i)])
33         if(p==0)
34             r=n;
35         else
36             r=min(n,k/p);
37         ans+=(r-i+1)*(q+k%r)/2;
38         i=r;
39     }
40     printf("%lld",ans);
41     return 0;
42 }

这可以称为整除分块例题了。。。

$$sum_{i=1}^nk\%i=sum_{i=1}^n(k-i*{lfloor}{frac{k}{i}}{ floor})=n*k-sum_{i=1}^n(i*{lfloor}{frac{k}{i}}{ floor})$$

最后的那个直接整除分块即可。。。

整除分块的话,就是因为${lfloor}{frac{k}{i}}{ floor}(i为整数且1<=i<=k)$的值只有$sqrt{k}$级别个

原因:对于小于等于$sqrt{k}$的i,每一个产生一个${lfloor}{frac{k}{i}}{ floor}$,最多产生$sqrt{k}$个;对于大于$sqrt{k}$的i,产生的${lfloor}{frac{k}{i}}{ floor}$都小于$sqrt{k}$,最多有$sqrt{k}$个。合起来也是$sqrt{k}$级别个

那么只要枚举每一种${lfloor}{frac{k}{i}}{ floor}$,根据要求的式子的一些性质计算即可

(比如此题就是在${lfloor}{frac{k}{i}}{ floor}$相等时就相当于$p*sum_{i=l}^ri$,$p$为那个${lfloor}{frac{k}{i}}{ floor}$)

当然不太可能直接枚举每一种${lfloor}{frac{k}{i}}{ floor}$,事实上还是枚举i,然后直接从i"跳"到j,使得j是满足${lfloor}{frac{k}{j}}{ floor}$与${lfloor}{frac{k}{i}}{ floor}$相等的最大数

如何跳?设${lfloor}{frac{k}{j}}{ floor}=p$,则$p+1>{frac{k}{j}}>=p$,则$j*(p+1)>k>=p*j$,则${frac{k}{p+1}}<j<={frac{k}{p}}$

因此枚举出一个i后,$p={lfloor}{frac{k}{i}}{ floor}$,那么要跳到的j就是${lfloor}{frac{k}{{lfloor}{frac{k}{i}}{ floor}}}{ floor}$

好吧,对于此题要加一个特判,因为n可能大于k,此时不能直接用原来代码,而i为整数且k<i<=n时${lfloor}{frac{k}{i}}{ floor}$的值为0,因此跳过即可;还有要"跳"到的j要和n取min

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<vector>
 5 using namespace std;
 6 #define fi first
 7 #define se second
 8 #define mp make_pair
 9 #define pb push_back
10 typedef long long ll;
11 typedef unsigned long long ull;
12 typedef pair<int,int> pii;
13 ll n,k,ans;
14 int main()
15 {
16     ll i,j;
17     scanf("%lld%lld",&n,&k);ans=n*k;
18     for(i=1;i<=min(n,k);i=j+1)
19     {
20         j=min(n,k/(k/i));//注意对n取min
21         ans-=(k/i)*(i+j)*(j-i+1)/2;
22     }
23     printf("%lld",ans);
24     return 0;
25 }
原文地址:https://www.cnblogs.com/hehe54321/p/7307069.html