Codeforces Round #182 (Div. 1) D:http://codeforces.com/contest/301/problem/D
题意:给一个1-n,n个数的序列,然后查询一个区间[l,r],问这个区间内有多少对:一个数是另外一个数的约数。
题解:这样的题目做的太少,自己也知道要用离线的数据结构,但是始终想不来,看了别人的代码也是半天没有看懂,最后还是请教了别人,才稍微明白一点。首先,对于[l,r]之间的数对来说,可以把1--r的数对减去1--l-1的数对,这是肯定的,但是这样还是多算了一部分,因为1--l-1之间的数和l--r之间的数也可以构成数对,所以必须把这部分减去。怎么减去呢,这里才是关键。首先,对于一次查询,把l和r分开来操作。我们可以枚举i,i从1到n,当枚举到i的时候,i之前的所有的数的倍数更新了,也就是说i之前的数对l--r之间的数已经形成,并且此时对l--r之间影响只有1--l-1之间的数,如果,我们把这部分数对减去的话,那么之前说的关键问题就解决了。减去之后就可以更新第i个数的倍数了,然后当i是查询的右端点的时候,这时候普就可以直接查询右端点,然后查询左端点然后做差累加上去,就可以得到最终的答案了。统计和更新直接用树状数组来维护就可以了。这道题,自己不会打,看了别人的代码,然后自己敲了一遍,确实是一道好题,值得好好品味。自己这方面的思维还没有培养起来呢,以后要多练练。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<vector> 6 using namespace std; 7 const int N=2e5+10; 8 int n,m; 9 int a[N],ct[N]; 10 int pos[N],l[N],r[N],ans[N]; 11 vector<int>ll[N],rr[N]; 12 void init(){ 13 memset(a,0,sizeof(a)); 14 memset(ct,0,sizeof(ct)); 15 memset(pos,0,sizeof(pos)); 16 } 17 int lowbit(int x){ 18 return x&(-x); 19 } 20 void add(int x){ 21 while(x<=n){ 22 ct[x]++; 23 x+=lowbit(x); 24 } 25 } 26 int getsum(int x){ 27 int ans=0; 28 while(x>0){ 29 ans+=ct[x]; 30 x-=lowbit(x); 31 } 32 return ans; 33 } 34 int main(){ 35 while(~scanf("%d%d",&n,&m)){ 36 init(); 37 for(int i=1;i<=n;i++){ 38 scanf("%d",&a[i]); 39 pos[a[i]]=i; 40 } 41 for(int i=1;i<=m;i++){ 42 scanf("%d%d",&l[i],&r[i]); 43 ll[l[i]].push_back(i); 44 rr[r[i]].push_back(i); 45 } 46 for(int i=1;i<=n;i++){ 47 for(int j=0;j<ll[i].size();j++){ 48 ans[ll[i][j]]-=getsum(r[ll[i][j]])-getsum(i-1); 49 } 50 for(int j=1;j*a[i]<=n;j++){ 51 add(pos[j*a[i]]); 52 } 53 for(int j=0;j<rr[i].size();j++){ 54 ans[rr[i][j]]+=getsum(i)-getsum(l[rr[i][j]]-1); 55 } 56 } 57 for(int i=1;i<=m;i++){ 58 printf("%d ",ans[i]); 59 } 60 } 61 }