洛谷 1447 [NOI2010]能量采集——容斥/推式子
题目:https://www.luogu.org/problemnew/show/P1447
1.容斥原理
求 f [ i ] 表示 gcd==i 的对数,先 f [ i ] = (n/i) * (m/i),再考虑减去不合法的对数。
不合法就是不互质,也就是还有别的公因数,即还能再除。直接算会重复,不如限定求出 gcd==j 的对数。
利用更大的 f [ ] 即可。在 n/i 和 m/i 的基础上 gcd==j 的对数就是 f [ i*j ]。所以要倒推。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=1e5+5; int n,m; ll ans,f[N],s[N]; int main() { scanf("%d%d",&n,&m); if(n>m)swap(n,m); for(int d=n;d;d--) { f[d]=(ll)(n/d)*(m/d);//!!!下取整! for(int k=2;k*d<=n;k++)f[d]-=f[k*d]; ans+=f[d]*(d*2-1); } printf("%lld\n",ans); return 0; }
2.推式子
∑ ∑ (gcd(i,j)*2-1) == ∑ ∑ ( ( ∑phi(d) )*2-1 ) == 2*∑ phi(d) ∑ ∑ - n*m == 2*∑ phi(d) * (n/d) * (m/d) - n*m。
值得注意的是phi[1]应该视作等于1。因为小于等于1的和1gcd==1的数有1个。
没错,d==1的时候就是把n和m都弄上了。考虑那个 d|i && d|j ,d==1就是出现在每个地方。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=1e5+5; int n,m,phi[N],pri[N],cnt; ll ans; int main() { scanf("%d%d",&n,&m);if(n>m)swap(n,m); for(int i=2;i<=n;i++) { if(!phi[i])pri[++cnt]=i,phi[i]=i-1; for(int j=1;j<=cnt&&i*pri[j]<=n;j++) if(i%pri[j]==0){phi[i*pri[j]]=phi[i]*pri[j];break;} else phi[i*pri[j]]=phi[i]*(pri[j]-1); ans+=(ll)phi[i]*2*(n/i)*(m/i); } ans+=2ll*n*m; printf("%lld\n",ans-(ll)n*m); return 0; }