[bzoj4635]数论小测验 解题报告

本文探讨了在算法设计中如何通过优化技巧提高效率,包括利用反演思想简化计算过程,使用容斥原理解决复杂问题,以及通过预处理降低时间复杂度等方法。

感觉自己好蠢只会反演。。跑了整整10s。。

ans=k=lri=1mkμ(i)mkin

这样时间复杂度就是O(mi=1i+mi=1mi)=O(m34).我还化来化去化了半天式子。。结果a了之后才发现原始的式子直接做就可以了。。
这样其实应该是O(m34logn)的,所以我们还需要预处理一下后面的幂,就可以做到O(T(m34+mlogn))了。

设f(k,x)表示[1,m]中与k的gcd是x的数的个数,则

ans=k=lr(mnd|kf(k,d)μ(kd)[dk])

按m从小到大离线处理询问,这样就可以处理f,花费O(m2logm),每次询问是O(mlogm),所以总时间复杂度是O(m2logm+TlogT+Tmlogm)

然后看了claris老司机的题解,感觉好神啊。。原来第一问可以直接考虑反面dp来搞,第二问可以先取反再容斥。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
#include<cstring>
#include<cmath>
#include<algorithm>
const int T=500+5,N=1e7+5;
const int Mod=1e9+7;
int t;

typedef long long LL;
LL power(LL prod,int x){
    LL ans=1;
    for(;x;x>>=1){
        if(x&1)ans=ans*prod%Mod;
        prod=prod*prod%Mod;
    }
    return ans;
}

namespace N1{
    const int M=1e7;
    bool p[M+5];
    int prime[M];
    int mu[M+5];
    int smu[M+5];

    const int R=5000+5;
    LL pwr1[R],pwr2[R];
    void work(){
        mu[1]=1;
        for(int i=2;i<=M;++i){
            if(!p[i]){
                prime[++prime[0]]=i;
                mu[i]=-1;
            }
            for(int j=1;j<=prime[0]&&i*prime[j]<=M;++j){
                p[i*prime[j]]=1;
                if(i%prime[j])mu[i*prime[j]]=-mu[i];
                else break;
            }
        }
        for(int i=1;i<=M;++i)smu[i]=smu[i-1]+mu[i];

        while(t--){
            int n,m,l,r;
            scanf("%d%d%d%d",&n,&m,&l,&r);

            int root=sqrt(m);
            for(int i=root;i;--i){
                pwr1[i]=power(i,n);
                pwr2[i]=power(m/i,n);
            }

            LL ans=0;
            for(int i=1,j,tmp=m/i;i<=m&&tmp>=l;i=j+1,tmp=m/i){
                j=m/tmp;

                //printf("---[%d,%d]---\n",i,j);

                LL s=0;
                for(int k=l,o;k<=min(tmp,r);k=o+1){
                    o=min(tmp/(tmp/k),r);
                    if(tmp/k<=root)s=(s+(o-k+1)*pwr1[tmp/k])%Mod;
                    else s=(s+(o-k+1)*pwr2[i*k])%Mod;

                    //printf("[%d,%d]=%I64d\n",k,o,(o-k+1)*power(tmp/k,n)%Mod);
                }
                ans=(ans+s*(smu[j]-smu[i-1]))%Mod;
            }
            printf("%lld\n",(ans+Mod)%Mod);
        }
    }
}
#include<vector>
namespace N2{
    const int M=1000;
    int cnt[M+5][M+5];

    bool p[M+5];
    int prime[M];
    int mu[M+5];

    int gcd[M+5][M+5];

    struct QS{
        int n,m,l,r,i;
        bool operator < (const QS & o)const{
            return m<o.m;
        }
    }que[T];
    LL ans[T];
    void work(){
        mu[1]=1;
        for(int i=2;i<=M;++i){
            if(!p[i]){
                prime[++prime[0]]=i;
                mu[i]=-1;
            }
            for(int j=1;j<=prime[0]&&i*prime[j]<=M;++j){
                p[i*prime[j]]=1;
                if(i%prime[j])mu[i*prime[j]]=-mu[i];
                else break;
            }
        }

        for(int i=M;i;--i)gcd[i][0]=gcd[0][i]=i;
        for(int i=1;i<=M;++i)
            for(int j=1;j<=i;++j)
                gcd[i][j]=gcd[j][i]=gcd[j][i%j];

        for(int i=1;i<=t;++i){
            scanf("%d%d%d%d",&que[i].n,&que[i].m,&que[i].l,&que[i].r);
            que[i].i=i;
        }
        sort(que+1,que+t+1);
        for(int i=1,j=0;i<=t;++i){
            for(;j<=que[i].m;++j)
                for(int o=M>>1;o;--o)
                    for(int k=o<<1;k<=M;k+=o)
                        if(mu[k/o]&&o%gcd[k][j]==0)
                            ++cnt[k][o];

            ans[que[i].i]=(que[i].r-que[i].l+1)*power(que[i].m,que[i].n)%Mod;
            for(int o=1;o<=que[i].r>>1;++o)
                for(int k=o<<1;k<=que[i].r;k+=o)
                    if(k>=que[i].l&&mu[k/o])
                        ans[que[i].i]=(ans[que[i].i]+mu[k/o]*power(cnt[k][o],que[i].n))%Mod;
        }
        for(int i=1;i<=t;++i)printf("%lld\n",(ans[i]+Mod)%Mod);
    }
}
int main(){
    freopen("bzoj_4635.in","r",stdin);
    //freopen("bzoj_4635.out","w",stdout);
    int type;
    scanf("%d%d",&t,&type);
    if(type==1)N1::work();
    else N2::work();
}

总结:
①两个根号枚举套一起是O(n34)的!
②直接dp确定值考虑反面是很方便的。如果把这种dp展开会发现是个容斥,但是如果直接从容斥的角度考虑就会变得非常麻烦了(因为会有什么至少、至多这种奇怪的东西)。
③虽然反演是一种容斥,但是有的时候不一定需要它,强行上反演反而会变得麻烦的。考虑一下应该具体怎么容斥。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值