Loading

【题解】「Ynoi2018」GOSICK [*hard]

这题为什么是 hard?显然因为这题贼卡常 = =

其实要处理的就是前缀的约数个数和和倍数个数和(后缀同样)。

等于要做的就是对于 \(a_i\) ,询问 \(a_{1,2,\cdots,i-1}\) 中有多少是 \(i\) 的约数,又有多少是 \(i\) 的倍数。(对于 \(a_i\) 相等的不计算)。

分别考虑约数和倍数怎么做。

  • 约数的做法

因为一个数的约数最多就那么多个 ......

所以直接暴力遍历约数就好了

  • 倍数的做法

这会儿不能暴力遍历倍数了,因为如果给你一堆的 1 的话会直接暴毙 = =

直接查询,加入一个数的时候遍历其约数即可。

  • 做一遍莫队,计算前后缀和贡献

这个直接做就好了吧?没什么复杂度的问题。

  • 修正答案

注意到这里按照最开始的办法做倍数是可以的,因为查询是 \(O(1)\) 的。

但是约数就不行。考虑怎么将约数的查询弄成 \(O(1)\) 或者找更好的办法。

(然后就不是很会做了 = =

考虑奇怪的做法:

  • \(a_i> L\) :暴力跳倍数。
  • \(a_i\leq L\):直接用二进制状态记录。

查询的时候,先处理每个数的 \(\leq L\) 的所有因子组成的状态,然后直接对标查询。

(ps:这个做法常数太大,过不去

#define debug

const int N=5e5+5;
const int M=7e6+5;

int n,m,a[N];
ll ans[N];
short facsta[N][4];
struct Query {int l,r,id;} q[N];

int edge_cnt,head[N<<2];
struct Edge {int nxt,to;} G[M];
inline void addedge(int u,int v) {G[++edge_cnt]=(Edge){head[u],v},head[u]=edge_cnt;}

namespace InitFactor { // {{{ Init Factor
    const int L=N-5;

    int cnt,d[N],prime[N];
    bool vis[N];
    pii hep[N];

    inline void sieve() { // Get d
        lep(i,2,L) {
            if(!vis[i]) prime[++cnt]=i,d[i]=i;
            for(int j=i+i;j<=L;j+=i)
                if(!vis[j]) d[j]=i,vis[j]=true;
        }
    }
    void dfs(int step,int rt,int res) {
        if(step>cnt) return addedge(rt,res),void();
        lep(i,0,hep[step].se) dfs(step+1,rt,res),res*=hep[step].fi;
    }
    inline void solve() {
        sieve();
        lep(i,1,L) {
            int now=i,las=-1,tot=0; cnt=0;
            while(now!=1) {
                if(d[now]==las) ++tot;
                else {if(~las) hep[++cnt]=mkp(las,tot); las=d[now],tot=1;}
                now/=d[now];
            }
            hep[++cnt]=mkp(las,tot),dfs(1,i,1);
        }
        lep(i,1,L) lep(j,0,7) lep(k,0,3) facsta[i][k]|=(i%(j+k*8+1)==0)<<j;
    }
} // }}}

namespace InitPreSuf { // {{{ Get Pre1,Suf1,Pre2,Suf2
    ll pre1[N],suf1[N],pre2[N],suf2[N];
    int sta[N];

    inline void solve() {
        CLEAR(sta); lep(i,1,n) { // pre1
            for(int e=head[a[i]];e;e=G[e].nxt) pre1[i]+=sta[G[e].to];
            ++sta[a[i]],pre1[i]+=pre1[i-1];
        }
        CLEAR(sta); rep(i,n,1) { // suf1
            for(int e=head[a[i]];e;e=G[e].nxt) suf1[i]+=sta[G[e].to];
            ++sta[a[i]],suf1[i]+=suf1[i+1];
        }
        CLEAR(sta); lep(i,1,n) { // pre2
            pre2[i]=pre2[i-1]+sta[a[i]];
            for(int e=head[a[i]];e;e=G[e].nxt) ++sta[G[e].to];
        }
        CLEAR(sta); rep(i,n,1) { // suf2
            suf2[i]=suf2[i+1]+sta[a[i]];
            for(int e=head[a[i]];e;e=G[e].nxt) ++sta[G[e].to];
        }
    }
}
using InitPreSuf::pre1;
using InitPreSuf::pre2;
using InitPreSuf::suf1;
using InitPreSuf::suf2;
// }}}

namespace FixAnswer { // {{{ Fix Answer
    struct Node {int i,l,r,id,typ;} sta1[N<<2],sta2[N<<2];
    int cnt1,cnt2;

    // {{{ Struct
    int sum[4][256],sta[N];

    inline void update(int x) {
        if(x<=32) {
            int id=(x-1)/8; x=1<<(x-id*8-1);
            lep(S,0,255) if(S&x) ++sum[id][S];
        } else for(int i=x;i<N;i+=x) ++sta[i];
    }
    inline int query(int x) {
        return sum[0][facsta[x][0]]+sum[1][facsta[x][1]]+
               sum[2][facsta[x][2]]+sum[3][facsta[x][3]]+sta[x];
    }
    // }}}

    inline void solve() {
        std::sort(sta1+1,sta1+1+cnt1,[](Node x,Node y){return x.i<y.i;});
        std::sort(sta2+1,sta2+1+cnt2,[](Node x,Node y){return x.i>y.i;});
        int l;
        
        l=1,CLEAR(sum),CLEAR(sta); lep(i,1,n) {
            update(a[i]);
            while(l<=cnt1&&sta1[l].i==i) {
                lep(j,sta1[l].l,sta1[l].r) ans[sta1[l].id]+=query(a[j])*sta1[l].typ;
                ++l;
            }
        }
        l=1,CLEAR(sum),CLEAR(sta); rep(i,n,1) {
            update(a[i]);
            while(l<=cnt2&&sta2[l].i==i) {
                lep(j,sta2[l].l,sta2[l].r) ans[sta2[l].id]+=query(a[j])*sta2[l].typ;
                ++l;
            }
        }
        
        l=1,CLEAR(sta); lep(i,1,n) {
            for(int e=head[a[i]];e;e=G[e].nxt) ++sta[G[e].to];
            while(l<=cnt1&&sta1[l].i==i) {
                lep(j,sta1[l].l,sta1[l].r) ans[sta1[l].id]+=sta[a[j]]*sta1[l].typ;
                ++l;
            }
        }
        l=1,CLEAR(sta); rep(i,n,1) {
            for(int e=head[a[i]];e;e=G[e].nxt) ++sta[G[e].to];
            while(l<=cnt2&&sta2[l].i==i) {
                lep(j,sta2[l].l,sta2[l].r) ans[sta2[l].id]+=sta[a[j]]*sta2[l].typ;
                ++l;
            }
        }

        lep(i,1,m) ans[i]+=ans[i-1];
    }
}
using FixAnswer::Node;
using FixAnswer::sta1;
using FixAnswer::sta2;
using FixAnswer::cnt1;
using FixAnswer::cnt2;
// }}}

namespace GetAnswer { // {{{ Get Answer
    inline void solve() {    
        int l=1,r=0;
        lep(i,1,m) {
            if(r<q[i].r) {
                ans[i]+=pre1[q[i].r]-pre1[r]+pre2[q[i].r]-pre2[r];
                if(l>1) sta1[++cnt1]=(Node){l-1,r+1,q[i].r,i,-1}; r=q[i].r;
            }
            if(r>q[i].r) {
                ans[i]-=pre1[r]-pre1[q[i].r]+pre2[r]-pre2[q[i].r];
                if(l>1) sta1[++cnt1]=(Node){l-1,q[i].r+1,r,i,1}; r=q[i].r;
            }
            if(l<q[i].l) {
                ans[i]-=suf1[l]-suf1[q[i].l]+suf2[l]-suf2[q[i].l];
                if(r<n) sta2[++cnt2]=(Node){r+1,l,q[i].l-1,i,1}; l=q[i].l;
            }
            if(l>q[i].l) {
                ans[i]+=suf1[q[i].l]-suf1[l]+suf2[q[i].l]-suf2[l];
                if(r<n) sta2[++cnt2]=(Node){r+1,q[i].l,l-1,i,-1}; l=q[i].l;
            }
        }
    }
} // }}}

int pos[N];
int main() {
    InitFactor::solve();

    IN(n,m);
    lep(i,1,n) IN(a[i]);
    lep(i,1,m) IN(q[i].l,q[i].r),q[i].id=i;

    int B=n/sqrt(m*2/3+1);
    std::sort(q+1,q+1+m,[&](Query a,Query b){return (a.l/B)^(b.l/B)?a.l<b.l:(((a.l/B)&1)?a.r<b.r:a.r>b.r);});
    lep(i,1,m) pos[q[i].id]=i;

    InitPreSuf::solve();
    GetAnswer::solve();
    FixAnswer::solve();

    lep(i,1,m) printf("%lld\n",ans[pos[i]]+q[pos[i]].r-q[pos[i]].l+1);
    return 0;
}

这个想法固然十分巧妙,但是过不去。

同时这个做法又很长 = =

考虑一种更简单的做法:根号分治:

  • 对于 \(a_i>\sqrt{n}\) :暴力跳倍数。
  • 对于 \(a_i\leq \sqrt{n}\) :预处理 \(sum_{x,i}\) 表示 \(a_{1,2,\cdots,i}\)\(x\) 的倍数个数,查询的话就遍历 \([1,\sqrt{n}]\) 中的每个数,然后看看在这段区间中的倍数个数即可。

需要注意的是虽然莫队的移动量是 \(O(n\sqrt{m})\) ,但是这个是对一段区间查询,因此查询总数是 \(O(m)\) 的,所以说虽然一次查询的复杂度是 \(O(\sqrt{n})\),这一块复杂度也还是 \(O(m\sqrt{n})\) 的。

然后没过几天 lxl 就改空间限制了 = = ,根本开不下。

首先有一种空间 \(O(n)\) 的做法:遍历 \([1,\sqrt{n}]\) 中的每一个数,然后做前缀和,接着在遍历一遍所有的询问,处理贡献。不难发现这个做法常数比较大。

接着优化:将数字分成 \(8\) 个一组,这样空间还是 \(O(n)\) 的(开 \(8\)\(sum\) 数组),然后将这 \(8\) 个数连着处理即可,这样就优秀不少。

还需要注意的是,因为常熟问题,暴力跳倍数的常数很小,因此可以做一些平衡:我实现的时候是将这个阈值设为了 \(80\)

(除了 FixAnswer,其他部分和上面一模一样 = =

const int T=80;

namespace FixAnswer { // {{{ Fix Answer
    struct Node {int i,l,r,id,typ;} seq1[N],seq2[N];
    int L,cnt1,cnt2,sta[N],tot[T+2],sum[8][N];

    inline void update(int x) {
        for(int e=head[x];e;e=G[e].nxt) ++sta[G[e].to];
        if(x>T) for(int i=x;i<N;i+=x) ++sta[i];
    }
    inline void query(Node now) {
        lep(i,now.l,now.r) ans[now.id]+=sta[a[i]]*now.typ;
    }
    inline void calc(Node now) {
        int res=0;
        lep(_,0,7) res+=tot[L+_]*(sum[_][now.r]-sum[_][now.l-1]);
        ans[now.id]+=res*now.typ;
    }

    inline void solve() {
        std::sort(seq1+1,seq1+1+cnt1,[](Node x,Node y){return x.i<y.i;});
        std::sort(seq2+1,seq2+1+cnt2,[](Node x,Node y){return x.i>y.i;});
        int l;

        l=1,CLEAR(sta); lep(i,1,n) {update(a[i]); while(l<=cnt1&&seq1[l].i==i) query(seq1[l]),++l;}
        l=1,CLEAR(sta); rep(i,n,1) {update(a[i]); while(l<=cnt2&&seq2[l].i==i) query(seq2[l]),++l;}
        
        lep(_,1,9) {
            L=(_-1)*8+1;
            lep(j,0,7) {
                const int num=L+j;
                lep(i,1,n) sum[j][i]=sum[j][i-1]+(a[i]%num==0);
            }

            l=1,CLEAR(tot); lep(i,1,n) {if(a[i]<=T) ++tot[a[i]]; while(l<=cnt1&&seq1[l].i==i) calc(seq1[l]),++l;}
            l=1,CLEAR(tot); rep(i,n,1) {if(a[i]<=T) ++tot[a[i]]; while(l<=cnt2&&seq2[l].i==i) calc(seq2[l]),++l;}
        }
        
        lep(i,1,m) ans[i]+=ans[i-1];
    }
} using FixAnswer::Node,FixAnswer::seq1,FixAnswer::seq2,FixAnswer::cnt1,FixAnswer::cnt2; // }}} 
posted @ 2020-12-20 23:16  Moonlightsqwq  阅读(104)  评论(0编辑  收藏  举报