[离线处理][scheduling][数论结论][x] HDU5869 Different GCD Subarray Query

题目链接

题意:

给定 $N$ 个数字, $Q$ 次询问,每次询问为查询下标在 $[l,r]$ 之间的所有数字中所有可能的子段的 $gcd$ 有多少种。$N,Q < 10^5 , a_i < 10^6$

题解:

首先一个重要的结论:以固定点为结尾的子段的 $gcd$ 种数不超过 $max(n,log(max(a_i)))$ 证明:每次往原有的序列里加入一个数字的话, $gcd$ 要么不变,要么至少变为原来的1/2,因此为指数级减小。

有了这个结论,我们可以试着来枚举一个端点考虑本题,不妨枚举每个段的右端点$r_j$ , 假设集合$S$表示以$r_{j-1}$ 为结尾的所有 $gcd$ ,那么要求出以$r_j$为结尾的所有 $gcd$ ,只需遍历S中的每个数字$d_k$,计算其与$a_j$ 的 $gcd$ ,放入新集合$S^{‘}$即可。这样的话我们可以得到一个空间为$O(nlogn)$的结果,表示以每个点为结尾的所有段的 $gcd$ 都是哪些数字。

现在来考虑查询。每次查询的话是要求出来$[l,r]$之间所有可能的 $gcd$ 。直接暴力不可取,由上面的结果可以试着考虑能否转化为前缀问题。可以发现,如果我们对所有的$[l,r]$离线处理,按$r$从小到大排序,并且按照这个顺序来调整已经遇到过的 $gcd$ 的信息的话,每次的查询只需要考虑有多少个 $gcd$ 出现的最右左端点大于等于l,这句话具体的意思是,一个 $gcd$ 可能出现过了很多次,不同的段都可以得到这个 $gcd$ ,但是我们只考虑这些段中左端点l_j最大的那个点$p_j$,这样的话如果$l<=p_j$那么可以确定$[l,r]$中一定有这个 $gcd$ (种类+1),这就就转化成了一个计数问题。

那么考虑如何来维护这个每个 $gcd$ 及其最右左端点。从我们推$O(nlogn)$的以每个点位结尾的 $gcd$ 集合来看,只需要在这个过程中为每个 $gcd$ 多维护一个表示最右左端点的域即可,更新就是把每次新产生的(以前从来没有见过的) $gcd$ 的这个域设为当前的下标i,其他的保持不变即可。由于$a_i$的最大值只有$10^6$,可以直接用一个数组$has[d]$来维护到目前为止$d$作为某个段的 $gcd$ 出现的最右位置。至于前后集合的递推可以用滚动数组,考虑到去重的话用两个$map$替代两个数组即可。

代码的话想清楚之后实现还是很简单的。

#include<bits/stdc++.h>

using namespace std;

const int maxn=1e5+5;
typedef long long ll;
int N,Q,MX;
map<int,int> mp[2];
map<int,int>::iterator it;
int has[maxn*10];

int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

vector<pair<int,int> > qs[maxn];
int tr[maxn*20];
void change(int x,int p){
    while(x<=MX){
        tr[x]+=p;
        x+=(x&(-x));
    }
}
ll sum(int x){
    ll res=0;
    while(x>0){
        res+=tr[x];
        x-=(x&(-x));
    }
    return res;
}
void move(int a,int b){
    change(a,-1);
    change(b,1);
}
int a[maxn],ans[maxn];
void init(){
    for(int i=0;i<=N;i++) qs[i].clear();
    mp[0].clear(),mp[1].clear();//final error
    memset(tr,0,sizeof(tr));
    memset(has,0,sizeof(has));
    MX=0;
}
int main(){
    int l,r,tmp,id,pt,pos;
    while(~scanf("%d%d",&N,&Q)){
        init();pt=0;
        for(int i=1;i<=N;i++){
            scanf("%d",&a[i]);
            MX=max(MX,a[i]);
        }
        for(int i=1;i<=Q;i++){
            scanf("%d%d",&l,&r);
            qs[r].push_back(make_pair(l,i));
        }
        
        for(int i=1;i<=N;i++,pt^=1){
            int len=qs[i].size();
            mp[pt].clear();
            mp[pt][a[i]]=i;
            for(it=mp[pt^1].begin();it!=mp[pt^1].end();++it){   
                tmp=gcd(a[i],it->first);
                if(mp[pt].find(tmp)!=mp[pt].end()){
                    mp[pt][tmp]=max(mp[pt][tmp],it->second);
                }else{
                    mp[pt][tmp]=it->second;
                }
            }
            for(it=mp[pt].begin();it!=mp[pt].end();++it){
                tmp=it->first,pos=it->second;
                if(has[tmp]){
                    if(has[tmp]==pos) continue;
                    move(min(has[tmp],pos),max(has[tmp],pos));
                    has[tmp]=max(has[tmp],pos);
                }else{
                    change(pos,1);
                    has[tmp]=pos;
                }
            }
            if(len){
                for(int j=0;j<len;j++){
                    l=qs[i][j].first,id=qs[i][j].second;
                    ans[id]=sum(i)-sum(l-1);
                }
            }
        }
        for(int i=1;i<=Q;++i){
            printf("%d\n",ans[i]);//error2
        }
    }
    
    return 0;
}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据