[luogu1447][bzoj2005][NOI2010]能量采集
題目大意
求出\(\sum_{i=1}^{n} \sum_{i=1}^{m} gcd(i,j)\times 2 -1\)。
題解
解法還是非常的巧妙的,我們考慮容斥原理。我們定義\(f[i]\)表示\(gcd(x,y)\)的數對的個數,但是我們可以發現這樣的狀態並不好直接轉移。那麽我們就從\(f[i]\)的倍數入手(也就是\(gcd(x,y)\)的倍數入手,這樣比較好理解),先定義\(g[i]\)為在數對\((x,y)\)中\(gcd(x,y)\)是\(i\)的倍數的個數。這種思想比較像線性篩素數。
對於一開始的\(g[i]\)就是\(\frac{n\times m}{i^2}\)。關於這個玩意的證明我還是不怎麽會,但是好像聽其他大佬說:你太弱了,這是顯而易見的。(emm~~我果然是太弱了)
那麽我們就當這個東西是顯而易見的好了,如果有證明我會回來補坑的。
證明
已知:\(x\in [1,n]\)且\(y\in [1,m]\)。
求證:\(gcd(x,y)\)的倍數(包括\(1\)倍)的個數有\(\frac{n\times m}{i^2}\)。
證明:
得到這些倍數之後,因為我們是算倍數,在\(g[i]\)中包含了\(g[i\times 2]+...+g[i\times k] \ (i\times k<=min(n,m))\),那麽容斥原理把這些重復的部分減去就可以了,也就是\(f[i]=f[i]-g[i\times2]-g[i\times3]-...-g[i\times k] \ (i\times k<=min(n,m))\)
小小的細節:因為我們是要算出倍數,那麽我們倍數必須要先算出來,那麽在枚舉是我們要從後向前枚舉,是不是非常好理解。還有的是最後的答案是\((i\times2-1)\times f[i]\)的累加,因為我們求得是個數,不是這個值(第一次寫寫錯了)。
ac代碼
# include <cstdio> # include <cstring> # include <algorithm> # include <ctype.h> # include <iostream> # include <cmath> # include <map> # include <vector> # include <queue> # define LL long long # define ms(a,b) memset(a,b,sizeof(a)) # define ri (register int) # define inf (0x7f7f7f7f) # define pb push_back # define fi first # define se second # define pii pair<int,int> # define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) using namespace std; inline int gi(){ int w=0,x=0;char ch=0; while(!isdigit(ch)) w|=ch=='-',ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return w?-x:x; } # define N 100005 int n,m; LL ans,f[N]; int main(){ n=gi(),m=gi(); for (int i=n;i>=1;i--){ f[i]=(LL)(n/i)*(m/i); for (int j=2*i;j<=min(n,m);j+=i) f[i]-=f[j];//減去重復的部分 ans+=(LL)(i*2-1)*f[i];//算出答案 } printf("%lld\n",ans); return 0; }
[luogu1447][bzoj2005][NOI2010]能量采集