C -求和公式(整除分块)

​ 先讲下整除分块是个啥:要求 i = 1 n \sum_{i=1}^n n/i 的值,这时候暴力需要O(n)的时间。由于这个区间是连续的,且’/'是向下取整,当i不能整除k时,n/i会等于最小的i(也就是区间最左边的值 L)除n的商。此时如果可以很快的找到这一个区间,那么就可以将时间复杂度降到O( n \sqrt{n} )。 接下来讲一下怎么去找这个区间:

​ 如果需要求 i = 1 20 20 i \sum_{i=1}^{20} \frac{20}{i} ,把这些要求的样例都写出来找找规律:

i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
20/i 20 10 6 5 4 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1

​ 看到这个表不难发现规律,用20再去除以 (20/i) 就等于最后一个等于这个值的数,比如说 当i=7时,20/i=2,那么用20/(20/7) = 10, 这个时候10就是20/i等于2的最后一个值。可以利用这个特性,在区间最左边用O(1)的时间就可以计算出区间最右边的坐标。在这个区间内,所有的值都是相同的,所以找到这个区间后,直接用区间长度乘以单个数值就ok。

#include<bits/stdc++.h>
using namespace std;

int n, ans;

int main() {
		scanf("%d", &n);
  	for(int l = 1, r;l <= n; l = r+1) {
      	r = n/(n/l); // 区间最右边
      	ans += (n/l) * (r-l+1);
    }
  	printf("%d\n", ans);
}

下一题:

余数求和 要求 i = 1 n \sum_{i=1}^n k%i。

i = 1 n \sum_{i=1}^n k%i

= i = 1 n \sum_{i=1}^n k-i*(k/i)*

=n*k - i = 1 n \sum_{i=1}^n i * (k/i)

​ 在每一段(L,R)中 k/i = k/L ,所以在相加的时候可以当作公因式提出来。 i = 1 n \sum_{i=1}^n i 相当于一个等差数列。由等差数列求和公式可得: (R-L+1) * (L+R) / 2。

​ 所以每一段(L,R)的和可以表示为 k/L * (R-L+1) * (L+R) / 2。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll n, k, ans;

int main() {
	scanf("%lld %lld", &n, &k);
	ans = n * k;
	for(ll l = 1,r;l <= n;l = r + 1) {
		if(k/l != 0) r = min(n, k/(k/l));
		else r = n;
		ans -= (k/l)*(r-l+1)*(l+r)/2;
	}
	printf("%lld\n", ans);
	return 0;
}

学长出的题目链接 求和公式

题意是要求 i = 1 n \sum_{i=1}^n j = 1 m \sum_{j=1}^m i 2 j i^2j (n % \% i) (m % \% j) % \% ( 1 0 9 + 7 10^9+7 ) 。 ($10 $ < = <= n, m $<=$1000000000)

​ 这个题的复杂度显然不能暴力求。 当时根本没有想到用别的方法觉得这个题不可能做得出来,之后听学长讲解,确实没那么难。(许多头铁的学弟用两重循环写了好久…)

​ 首先题面给的这个式子肯定是不能直接用的,对它变下形:

i = 1 n \sum_{i=1}^n j = 1 m \sum_{j=1}^m i 2 j i^2j (n % \% i) (m % \% j) % \% ( 1 0 9 + 7 10^9+7 )

= i = 1 n \sum_{i=1}^n i 2 i^2 ( n % i n\% i ) * j = 1 m \sum_{j=1}^m j ( m % j ) j(m\%j) % \% ( 1 0 9 + 7 10^9+7 )

= i = 1 n \sum_{i=1}^n i 2 i^2 ( n i ( n / i ) n-i*(n/i) ) * j = 1 m \sum_{j=1}^m j ( m j ( m / j ) ) j(m-j*(m/j)) % \% ( 1 0 9 + 7 10^9+7 )

= i = 1 n \sum_{i=1}^n i 2 i^2 n - i 3 ( n / i ) i^3*(n/i) * j = 1 m \sum_{j=1}^m j m j 2 ( m / j ) j*m-j^2*(m/j) % \% ( 1 0 9 + 7 10^9+7 )

第一个求和符号中的前半部分将n提出,就是一个 n 2 n^2 的求和(不会的看下面给的链接)

n i = 1 n i 2 i = 1 n i 3 ( n / i ) n* \sum_{i=1}^n i^2 - \sum_{i=1}^n i^3(n/i)

n n ( n + 1 ) ( 2 n + 1 ) 6 n*\frac{n*(n+1)*(2*n+1)}{6} - i = 1 n i 3 ( n / i ) \sum_{i=1}^n i^3(n/i)

第二个求和符号中的前半部分就是一个等差数列求和,这个应该不难:

m m ( 1 + m ) 2 j = 1 m j 2 ( m / j ) m*\frac{m*(1+m)}{2}-\sum_{j=1}^mj^2(m/j)

不会 i = 1 n \sum_{i=1}^n i 2 i^2 看这个推导

不会 i = 1 n i 3 \sum_{i=1}^n i^3 看这个推导

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1e9+7;

ll n, m;

ll powmod(ll a,ll b)
{
	ll res = 1;
	for(;b;b>>=1)
	{
		if(b&1) res = res * a % mod;
		a = a * a % mod;
	}
	return res;
}

ll inv(ll x)
{
	return powmod(x,mod-2);
} //上面两个函数是求逆元的 用这个方法来代替除法,避免除法向下取整会对算出的值造成偏差


ll sum_2(ll p) { // i^2 从1加到p的公式
	return p*(p+1)%mod*(2*p+1)%mod*inv(6)%mod;
}

ll sum_3(ll p) { // i^3 从1加到p的公式
	return p*p%mod*(p+1)%mod*(p+1)%mod*inv(4)%mod;
}

int main() {
	scanf("%lld %lld", &n, &m);
	
	ll num1 = n*sum_2(n)%mod; // 求第一个求和公式的值
	for(ll l = 1,r;l <= n; l = r+1) { 
		r = n/(n/l);
		ll t = (sum_3(r)-sum_3(l-1)+mod)%mod;
		t = t * (n/l) %mod;
		num1 = (num1 - t + mod) % mod;
	}
	
	ll num2 = m*m%mod*(1+m)%mod*inv(2)%mod; // 求第二个求和公式的值
	for(ll l = 1,r;l <= m; l = r+1) {
		r = m/(m/l);
		ll t = (sum_2(r)-sum_2(l-1)+mod)%mod;
		t = t *(m/l) %mod;
		num2 = (num2 - t + mod) % mod;
	}
	
	printf("%lld\n", num1*num2%mod);
}
发布了6 篇原创文章 · 获赞 7 · 访问量 566

猜你喜欢

转载自blog.csdn.net/weixin_45419138/article/details/103446724
今日推荐