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

​ 先講下整除分塊是個啥:要求i=1n\sum_{i=1}^nn/i 的值,這時候暴力需要O(n)的時間。由於這個區間是連續的,且’/'是向下取整,當i不能整除k時,n/i會等於最小的i(也就是區間最左邊的值 L)除n的商。此時如果可以很快的找到這一個區間,那麼就可以將時間複雜度降到O(n\sqrt{n})。 接下來講一下怎麼去找這個區間:

​ 如果需要求i=12020i\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=1n\sum_{i=1}^nk%i。

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

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

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

​ 在每一段(L,R)中 k/i = k/L ,所以在相加的時候可以當作公因式提出來。i=1n\sum_{i=1}^ni 相當於一個等差數列。由等差數列求和公式可得: (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=1n\sum_{i=1}^nj=1m\sum_{j=1}^m i2ji^2j (n%\%i) (m%\%j) %\% (109+710^9+7) 。 ($10 $<=<=n, m $<=$1000000000)

​ 這個題的複雜度顯然不能暴力求。 當時根本沒有想到用別的方法覺得這個題不可能做得出來,之後聽學長講解,確實沒那麼難。(許多頭鐵的學弟用兩重循環寫了好久…)

​ 首先題面給的這個式子肯定是不能直接用的,對它變下形:

i=1n\sum_{i=1}^nj=1m\sum_{j=1}^m i2ji^2j (n%\%i) (m%\%j) %\% (109+710^9+7)

= i=1n\sum_{i=1}^ni2i^2(n%in\% i) * j=1m\sum_{j=1}^m j(m%j)j(m\%j) %\% (109+710^9+7)

= i=1n\sum_{i=1}^ni2i^2(ni(n/i)n-i*(n/i)) * j=1m\sum_{j=1}^m j(mj(m/j))j(m-j*(m/j)) %\% (109+710^9+7)

=i=1n\sum_{i=1}^n i2i^2n - i3(n/i)i^3*(n/i) * j=1m\sum_{j=1}^m jmj2(m/j)j*m-j^2*(m/j) %\% (109+710^9+7)

第一個求和符號中的前半部分將n提出,就是一個n2n^2 的求和(不會的看下面給的鏈接)

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

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

第二個求和符號中的前半部分就是一個等差數列求和,這個應該不難:

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

不會i=1n\sum_{i=1}^n i2i^2看這個推導

不會i=1ni3\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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章