已经有函数F(n)=∑ f(d),可以导出 f(n)= ∑ μ(d)F(n/d)
d|n d|n
已经有F(n)= ∑ f(d),可以导出f(n)= ∑ μ(d/n)F(d)
n|d n|d
bzoj2301 Problem b
题目大意:求gcd(x,y)=k(a<=x<=b,c<=y<=d)的数对个数。
思路:F(i)表示i|gcd(x,y)的数对个数,f(i)表示gcd(x,y)的数对个数,我们要求的就是f(k)。F(i)相对好求一些,F(i)=⌊n/i⌋⌊m/i⌋,
f(i)=∑(i|a)μ(a/i)F(a)=∑(i|a)μ(a|i)⌊n/a⌋⌊m/a⌋ ,然后我们只需要枚举⌊n/a⌋⌊m/a⌋的值就可以了(这个值据说是2(根n+根m))。我们穷举a,
找到所有⌊n/a⌋⌊m/a⌋相等的的位置(连续的),然后乘上预处理出来的mu函数的和就可以了。这里有一个小技巧,1...x的数列中,与n/i相等的值的最
后一位是(n/(n/i)),这样我们找到n、m右边界较小的就是一段相等的区间了。
同时,对于求一个区间内gcd=k的,我们可以通过将区间/k后找到互质的数对的个数就是答案了。
再同时,对于这道题中的答案要容斥原理一下,每次都处理成[1...x][1...y],然后答案就是work(b,d)-work(a,d)-work(c,b)+work(a,c)了。
这题中的技巧和化简思路都十分重要,这种穷举除数的思路据(某大神)说十分常用。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int mu[50010]={0},prime[50010]={0},k; bool flag[50010]={false}; void prework(int n) { int i,j; mu[1]=1; for (i=2;i<=n;++i) { if (!flag[i]) { prime[++prime[0]]=i;mu[i]=-1; } for (j=1;j<=prime[0]&&i*prime[j]<=n;++j) { flag[i*prime[j]]=true; if (i%prime[j]==0) { mu[i*prime[j]]=0;break; } else mu[i*prime[j]]=-mu[i]; } } for (i=1;i<=n;++i) mu[i]+=mu[i-1]; } int work(int x,int y) { int i,last=0,ans=0; x/=k;y/=k; if (x>y) swap(x,y); for (i=1;i<=x;i=last+1) { last=min(x/(x/i),y/(y/i)); ans+=(x/i)*(y/i)*(mu[last]-mu[i-1]); } return ans; } int main() { int n,a,b,c,d,i,ans; prework(50000); scanf("%d",&n); for (i=1;i<=n;++i) { scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); ans=work(b,d)-work(a-1,d)-work(c-1,b)+work(a-1,c-1); printf("%d\n",ans); } }
bzoj3853GCD Array
题目大意:在一个数列上进行操作。1)读入p,d,v,对所有gcd(i,p)=d的ai+v;2)求$\sum_{i=1}^xa_i$.
思路:听了题解才明白是反演。我们对于一次修改操作可以看作$a_i+=[gcd(i,p)=d]v$,对上式进行化简可以得到
[gcd(i,p)=d]v=[gcd (\frac{i}{d},\frac{p}{d})=1]v
=v\sum_{e|\frac{i}{d},e| \frac{p}{d} } \mu{e}
= v\sum_{ed|i,e|\frac{p}{d}}\mu{e}
=\sum_{ed|i , e| \frac{p}{d}} \mu{e}v
我们维护一个数组$f_e$,令$a_i=\sum_{e|i}f_e$,修改的时候我们每次穷举$\frac{p}{d}$的约数x(也就是e),x*d的位置加上x*v,用树状数组维护;查询的时候ANS=\sum_{i=1}^xa_i
=\sum_{i=1}^x\sum_{e|i}f(e)
=\sum_{i=1}^x\left \lfloor\frac{x}{i}\right \rfloor f(i)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define maxnode 200005 using namespace std; long long cc[maxnode]={0}; int n,prime[maxnode]={0},mu[maxnode]={0}; vector<int> yue[maxnode]; bool flag[maxnode]={false}; void pre() { int i,j; mu[1]=1; for (i=2;i<maxnode;++i) { if (!flag[i]) { prime[++prime[0]]=i;mu[i]=-1; } for (j=1;prime[j]*i<maxnode&&j<=prime[0];++j) { flag[prime[j]*i]=true; if (i%prime[j]) mu[i*prime[j]]=-mu[i]; else { mu[i*prime[j]]=0;break; } } } } void getyue() { int i,j; for (i=1;i<=maxnode;++i) yue[i].clear(); for (i=1;i<maxnode;++i) for (j=i;j<maxnode;j+=i) yue[j].push_back(i); } int lowbit(int x){return x&(-x);} long long ask(int x,int y) { long long sum=0; --x; for (;y;y-=lowbit(y)) sum+=cc[y]; for (;x;x-=lowbit(x)) sum-=cc[x]; return sum; } void change(int x,int v) { for (;x<=n;x+=lowbit(x)) cc[x]+=(long long)v; } int main() { int m,i,j,p,v,d,op,ci=0; long long ans; pre();getyue(); while(scanf("%d%d",&n,&m)) { if (n==0&&m==0) break; memset(cc,0,sizeof(cc));++ci; printf("Case #%d:\n",ci); for (i=1;i<=m;++i) { scanf("%d",&op); if (op==1) { scanf("%d%d%d",&p,&d,&v); if (p%d) continue; p=p/d; for (j=0;j<yue[p].size();++j) change(yue[p][j]*d,mu[yue[p][j]]*v); } else { scanf("%d",&p);ans=0; for (j=1;j<=p;j=d+1) { d=(p/(p/j)); ans+=(long long)(p/j)*ask(j,d); } printf("%lld\n",ans); } } } }
bzoj2005 noi2010能量采集
题目大意:n×m的矩阵中,有n×m个点位于格点上,对于每个点,采集的能量是这个点和(0,0)连线上点的个数(不包含端点)×2+1,求采集所有点的能量和。
思路:对于一个点(x,y),采集的能量是2×(gcd(x,y)-1)+1,求这个的sigma。可以用反演,穷举1~min(n,m)作为gcd的值,求出个数后乘i加起来乘2-1就是答案了。(反演的过程同第一题)。
其实并不需要这么麻烦,我们在反演中F(d)表示d|gcd(x,y)的个数,F(d)=(n/d)(m/d),那么我们用这个数减去gcd=2d、3d……id的个数就可以了,用f(d)表示gcd=d的个数,就是用F(d)-f(2d)-f(3d)-……f(id),我们倒着穷举gcd,就可以完成了。不需要反演和求mu,速度还++。。。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 #define up 100000 using namespace std; int mu[maxnode]={0},prime[maxnode]={0}; long long sum[maxnode]={0}; bool flag[maxnode]={0}; void getmu() { int i,j; mu[1]=1; for (i=2;i<=up;++i) { if (!flag[i]) { prime[++prime[0]]=i;mu[i]=-1; } for (j=1;j<=prime[0]&&i*prime[j]<=up;++j) { flag[i*prime[j]]=true; if (i%prime[j]==0){mu[i*prime[j]]=0;break;} else mu[i*prime[j]]=-mu[i]; } } for (i=1;i<=up;++i) sum[i]=sum[i-1]+(long long)mu[i]; } long long ask(int n,int m,int k) { int i,j,last=0; long long ans=0; n/=k;m/=k;if (n>m) swap(n,m); for (i=1;i<=n;i=last+1) { last=min(n/(n/i),m/(m/i)); ans+=(long long)(n/i)*(long long)(m/i)*(sum[last]-sum[i-1]); } return ans; } int main() { int n,m,i,j; long long ans=0; scanf("%d%d",&n,&m); if (n>m) swap(n,m);getmu(); for (i=1;i<=n;++i) ans+=i*ask(n,m,i); ans=ans*2-(long long)n*(long long)m; printf("%I64d\n",ans); }