zoukankan      html  css  js  c++  java
  • [HAOI2018]奇怪的背包 (DP,数论)

    [HAOI2018]奇怪的背包



    $ solution: $

    首先,这一道题目的描述很像完全背包,但它所说的背包总重量是在模P意义下的,所以肯定会用到数论。我们先分析一下,每一个物品可以放无数次,可以达到的背包重量其实就是所有 $ gcd(a[i],P) $ 的倍数。 这一点和天天爱跑步简直神似!因为天天爱跑步中每一个人也可以走无数步,跑到环形(就是模意义下)。

    但是这道题目还可以加入多种物品,我们不难发现,如果加入i和j两种物品,它所能达到的重量其实只是在gcd中多加了一个,就是所有 $ gcd(a[i],a[j],P) $ 的倍数。这个性质在加入更多物品后依然成立。所以我们只在乎每种物品加或不加,且状态可以用P的所有约数表示(因为加入物品后能达到的重量一定是所有物品重量和P全部取gcd后的倍数)(我们只需记录这个约数即可)而我们发现P的约数个数小于3000(一般一个数的约数个数不会超过它本身的三分之一次方),所以我们可以用这个状态来完全背包:

    我们定义 $ f[i][j] $ 表示已经完全背包跑完前i个物品,现在放入物品的总约数为j的方案数。然后我们发现数据范围太大了,跑不了!这怎么办? 我们发现每一个物品的贡献其实就是它的重量和P的公约数,而P的约数个数小于3000,我们可以在读入的时候就让它和P取gcd,这样会有很多物品的贡献重复(我们开个桶归类)然后每一次都按P的约数来跑完全背包。(注意要将P的约数离散化,即表示为P的第几个约数)

    不过这样每一次加入某一些与P的公约数为P的第i个约数的物品时,可以取这多个物品中的某一个或多个(注意可以不选,需要加个1),所以还要乘上一个 $ (2^{物品种类数)}-1) $ (这是因为与P的公约数为P的第i个约数的物品有很多,每一个我都可以选或不选,于是有 $ 2^{物品种类数)} $ 个,然后再减去全部不选的那一种就要减一)。

    $ f[i][j]=f[i-1][j]+(1+sum_{gcd(a[k],a[i])==a[j]}{f[i-1][k]}) imes (2^{tot[i]}-1) $

    然后处理答案时,我们直接枚举一遍所有P的约数,然后在是这个约数倍数的答案处加上相应贡献即可!(这里有一个小优化,和我们读入一样,我们1~q以内的所有数的答案,其实就是它和P的公约数的答案!)



    $ code: $

    #include<iostream>
    #include<cstdio>
    #include<iomanip>
    #include<algorithm>
    #include<cstring>
    #include<cstdlib>
    #include<ctime>
    #include<cmath>
    #include<vector>
    #include<queue>
    #include<map>
    #include<set>
    
    #define ll long long
    #define db double
    #define f0 f[now^1]
    #define f1 f[now]
    #define rg register int
    
    using namespace std;
    
    const int mod=1e9+7;
    
    int n,m,p,tt,now;
    int s[3005];
    int t[3005];
    int g[3005];
    int a[1000005];
    int pf[1000005];
    int f[2][3005];
    
    inline int qr(){
        char ch;
        while((ch=getchar())<'0'||ch>'9');
        int res=ch^48;
        while((ch=getchar())>='0'&&ch<='9')
            res=res*10+(ch^48);
        return res;
    }
    
    inline int gcd(int x,int y){
        rg z;
        while(y){z=x;x=y,y=z%y;}
        return x;
    }
    
    inline int find(int x){
        rg l=1,r=tt,mid;
        while(l<=r){
            mid=(l+r)>>1;
            if(x<s[mid])r=mid-1;
            else l=mid+1;
        }return r;
    }
    
    int main(){
        //freopen(".in","r",stdin);
        //freopen(".out","w",stdout);
        n=qr(); m=qr(); p=qr(); pf[1]=2;
        for(rg i=1;i<=n;++i) a[i]=gcd(qr(),p);
        for(rg i=1,j=sqrt(p);i<=j;++i) if(p%i==0)s[++tt]=i;
        for(rg i=tt;i;--i) s[++tt]=p/s[i];
        for(rg i=1;i<=n;++i) pf[i+1]=(pf[i]<<1)%mod,--pf[i];
        for(rg i=1;i<=n;++i) ++t[find(a[i])];
        for(rg i=1;i<=tt;++i){
            if(!t[i])continue;else now^=1;
            for(rg j=1;j<=tt;++j)f1[j]=f0[j];
            for(rg j=1;j<=tt;++j){
                if(!f0[j])continue;
                rg gg=find(gcd(s[i],s[j]));
                f1[gg]=(f1[gg]+(ll)f0[j]*pf[t[i]])%mod;
            }f1[i]=(f1[i]+pf[t[i]])%mod;
        }
        for(rg i=1;i<=tt;++i)
            for(rg j=1;j<=tt;++j)
                if(s[i]%s[j]==0)g[i]=(g[i]+f1[j])%mod;
        for(rg i=1;i<=m;++i)
            printf("%d
    ",g[find(gcd(qr(),p))]);
        return 0;
    }
    
    
  • 相关阅读:
    揭秘富人的22种习惯与风格
    CSS
    浏览器--编辑器
    3 位运算 , 补码 ----在开发中比较少用
    2 Java数据类型+转义字符
    1 概述
    如何使用大脑
    JDBC
    web.xml文件的作用
    WindowBuilder插件探索
  • 原文地址:https://www.cnblogs.com/812-xiao-wen/p/10689357.html
Copyright © 2011-2022 走看看