1. 程式人生 > 其它 >[清華集訓2012]模積和(數論分塊)

[清華集訓2012]模積和(數論分塊)

綜合性的數論分塊練習題,然而賽時並不知道平方數列求和公式……

\(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[i\ne j](n\mod i)(m\mod j)\), 對 \(19940417\) 取模(至今未考證出是什麼日子)。\(1\le n,m\le 10^9\)

先來看看這個式子有什麼特點,然後開拆:

  • \([i\ne j]\) 看起來需要容斥掉;
  • 取模似乎可以轉化為下取整,數論分塊——資料範圍驗證了這一點。

下面進行推導:

不妨設 \(n\le m\),原式可變形為 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(n\mod i)(m\mod j)-\sum\limits_{i=1}^{n}(n\mod i)(m\mod i)\)

;
交換求和符號順序,有 \(\sum\limits_{i=1}^{n}(n\mod i)\sum\limits_{j=1}^{m}(m\mod j)-\sum\limits_{i=1}^{n}(n\mod i)(m\mod i)\);
將取模轉換為下取整形式:\(\sum\limits_{i=1}^{n}(n-i \lfloor \frac{n}{i}\rfloor)\sum\limits_{j=1}^{m}(m-j \lfloor \frac{m}{j}\rfloor)-\sum\limits_{i=1}^{n}(n-i \lfloor \frac{n}{i}\rfloor)(m-j\lfloor \frac{m}{j}\rfloor)\)
;
拆開第二大項的括號,有\(\sum\limits_{i=1}^{n}(n-i \lfloor \frac{n}{i}\rfloor)\sum\limits_{j=1}^{m}(m-j \lfloor \frac{m}{j}\rfloor)-\sum\limits_{i=1}^{n}(n m-m i \lfloor \frac{n}{i}\rfloor-n i \lfloor \frac{m}{i}\rfloor+i^2\lfloor \frac{n}{i}\rfloor\lfloor \frac{m}{i}\rfloor)\)

不需要再拆了,這個又臭又長的式子已經可以用數論分塊處理了。

有一個小問題:第二大項最後的 \(i^2\)
怎麼處理?

平方和數列求和公式:\(\sum\limits_{i=1}^{n}=\frac{n(n+1)(2n+1)}{6}\)。數歸顯然可證。

下面是 AC 程式碼:

#define int long long
const int p=19940417;
int m1(int x){return (x*(x+1)>>1)%p;}//等差數列求和
int m2(int x){//平方和數列求和
	return x%3==1?(x*(x+1)>>1)%p*((x<<1|1)/3)%p:(x*(x+1)/6)%p*(x<<1|1)%p;
}
int f(int x){//數論分塊,g()同
	int ans=0;
	for(int l=1,r,len;l<=x;l=r+1){
		r=x/(x/l),len=r-l+1;
		(ans+=len*x%p-(x/l)%p*(m1(r)-m1(l-1)))%=p;
	}
	return (ans+p)%p;
}
int g(int x,int y){
	int ans=0;
	for(int l=1,r,len;l<=x&&l<=y;l=r+1){
		r=min(x/(x/l),y/(y/l)),len=r-l+1;//因為有兩個下取整相乘,故取min保證正確性
		(ans+=x*y%p*len)%=p;
		(ans-=(x*(y/l)+y*(x/l))%p*(m1(r)-m1(l-1)))%=p;
		(ans+=(x/l)*(y/l)%p*(m2(r)-m2(l-1)))%=p;
	}
	return (ans+p)%p;
}
signed main(){
	int n=rd(),m=rd();
	printf("%lld\n",(f(n)*f(m)%p-g(n,m)+p)%p);
	return 0;
}

THE END