题意:给定一个11~nn的全排列AA,若干个询问,每次询问给出一个区间[l,r][l,r],要求得出∑l≤i<j≤r gcd(Ai,Aj)的值。
解法:这题似乎做的人不是很多,蒟蒻当然不会做只能看题解了qwq,目前看到一个比较好的题解是https://blog.csdn.net/Maxwei_wzj/article/details/79355887。
没什么好说的,首先必须先推式子。
那么这一坨东西到底是什么意思呢?其实就是对于每个数d,sigma[d|ai][d|aj] 是从区间[l,r]中选两个数且都有约数d的方案数,然后phi(d)乘以这个方案数,对所有的d求和就是答案了。
然后问题是怎么求从区间[l,r]中选两个数且都有约数d的方案数?考虑[l,r]中的数存在约数d的数个数为c[d],那么显然方案数就是c[d]*(c[d]-1)/2。那么问题又变成了怎么快速维护[l,r]中的数存在约数d的数个数呢?对于这题只有询问的题目我们可以考虑使用莫队算法:先预处理每个数的约数,然后每增加/减少一个数就枚举它的所有约数计算贡献即可。
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=2e4+10,B=233; #define bel(x) ((x-1)/B+1) int n,m,a[N]; LL sum,c[N],ans[N]; struct query{ int id,l,r; bool operator < (const query &rhs) const { return bel(l)==bel(rhs.l) ? r<rhs.r : l<rhs.l; //询问排序顺序 } }q[N]; bool notp[N]; int pnum, p[N], phi[N]; vector<int> fac[N]; void prework(int n) { memset(notp, 0, sizeof notp); pnum = 0; phi[1]=1; for (int i = 2; i <= n; i++) { if (!notp[i]) { p[pnum++] = i; phi[i] = i - 1; } for (int j = 0; j < pnum && i * p[j] <= n; j++) { int k = i * p[j]; notp[k] = 1; if (i % p[j] == 0) { phi[k] = phi[i] * p[j]; break; } phi[k] = phi[i] * (p[j] - 1); } } for (int i=1;i<=n;i++) for (int j=i;j<=n;j+=i) fac[j].push_back(i); } void add(int x) { //添加操作 for (int i=0;i<fac[x].size();i++) { int y=fac[x][i]; sum+=c[y]*phi[y]; c[y]++; } } void del(int x) { //删除操作 for (int i=0;i<fac[x].size();i++) { int y=fac[x][i]; c[y]--; sum-=c[y]*phi[y]; } } void solve() { //莫队算法 int pl=1,pr=0; sum=0; for (int i=1;i<=m;i++) { while (pl<q[i].l) del(a[pl++]); while (pl>q[i].l) add(a[--pl]); while (pr<q[i].r) add(a[++pr]); while (pr>q[i].r) del(a[pr--]); ans[q[i].id]=sum; } } int main() { prework(20000); int T,cas=0; cin>>T; while (T--) { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); scanf("%d",&m); for (int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i; sort(q+1,q+m+1); for (int i=1;i<=n;i++) c[i]=0; solve(); printf("Case #%d: ",++cas); for (int i=1;i<=m;i++) printf("%lld ",ans[i]); } return 0; }