題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=4676
解題思路:總體上這是一個數學題加上離線與分塊處理加速。顯然對於區間內有公約數x的數來說,假設有num[x]個x的倍數,那麼答案顯然是sigma(C(num[x],2)*f(x)),根據容斥原理f(x)應該是一個小於x的數並且滿足sigma(f(d)) = x{d|x},因為有x這個約數的數對一定也有d這個約數,其中d|x,所以我在加上C(num[x],2)*f(x)之前就把C(num[d],2)*f(d)加上了,而總共對於x所需累加的次數是x,所以有sigma(f(d))
= x{d|x},而這正是麥比烏斯反演公式,可得f(d)為歐拉函數,http://www.isnowfy.com/mobius-inversion/
然後每加入一個數我們只要加上num[x]*phi[x]就行了(根據組合公式的性質比較容易看出)。但是直接暴力的話複雜度會超,就是對於查詢區間大幅波動的情況就需要不停的加入點,刪除點,複雜度估計能達到n平方,所以我們採用離線查詢的方式,然後對查詢左端點分段排,在每一段內保證查詢右端點的有序性,這樣就可以大致保持查詢左端點和右端點的有序性。下面上代碼:
#include<cstdio>#include<iostream>#include<vector>#include<cmath>#include<cstring>#include<algorithm>#define N 20005using namespace std;int phi[N],a[N],num[N],ans[N];int L,R,ret;vector<int>d[N];struct node{ int l,r,id,s;}seg[N];void init(){ for(int i = 1;i<N;i++)phi[i] = i; for(int i = 2;i<N;i++) if(phi[i] == i) for(int j = i;j<N;j+=i) phi[j] = phi[j]/i*(i-1); for(int i = 1;i<N;i++) for(int j = i;j<N;j+=i) d[j].push_back(i);}void Insert(int x){ for(int i = 0;i<d[x].size();i++) { int v = d[x][i]; ret+=num[v]*phi[v]; num[v]++; }}void Delete(int x){ for(int i = 0;i<d[x].size();i++) { int v = d[x][i]; num[v]--; ret-=num[v]*phi[v]; }}void query(int l,int r){ for(int i = l;i<L;i++)Insert(a[i]); for(int i = L;i<l;i++)Delete(a[i]); for(int i = r+1;i<=R;i++)Delete(a[i]); for(int i = R+1;i<=r;i++)Insert(a[i]);}bool cmp(node x,node y){ if(x.s == y.s)return x.r<y.r; return x.s<y.s;}int main(){ int t,y,n,m,i,j,cas = 1; scanf("%d",&t); init(); while(t--) { scanf("%d",&n); int size = sqrt(1.0*n); memset(num,0,sizeof(num)); for(i = 1;i<=n;i++) scanf("%d",&a[i]); scanf("%d",&m); for(i = 1;i<=m;i++) { scanf("%d%d",&seg[i].l,&seg[i].r); seg[i].id = i; seg[i].s = seg[i].l/size; } sort(seg+1,seg+m+1,cmp); ret = 0; L = seg[1].l; R = seg[1].r; for(i = L;i<=R;i++)Insert(a[i]); ans[seg[1].id] = ret; for(i = 2;i<=m;i++) { query(seg[i].l,seg[i].r); L = seg[i].l; R = seg[i].r; ans[seg[i].id] = ret; } printf("Case #%d:\n",cas++); for(i=1;i<=m;i++) printf("%d\n",ans[i]); } return 0;}