1. 程式人生 > >[luogu1447][bzoj2005][NOI2010]能量采集

[luogu1447][bzoj2005][NOI2010]能量采集

rst span rac 累加 str mem 定義 包含 lin

技術分享圖片

題目大意

求出\(\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]能量采集