• 【NOI OL #3】优秀子序列


    题目链接

    首先我们看两个子序列不同是指下标不同,然后答案只跟子序列之和有关,发现这题跟序列的位置无关,那我们直接对数值考虑,开桶$c_x$表示有$c_x$个$x$元素。

    我们发现,每个元素$a_ile 2 imes 10^5$,而一个优秀的子序列里的元素必须满足不能有相同的二进制位,所以子序列和不会超过$2^{18}$。对于一组数据来说,设最大元素的二进制长度为$d$,那么这个和的最大值就不会超过$2^d$,然后所有子序列元素总和组成的全集就是$m=2^d-1$。接下来可能会出现集合和二进制说法混用的情况,意会一下。

    然后我们发现,相同元素和的子序列对答案的贡献是一样的,那我们立马想到可以DP,转移出每种总和的子序列个数,简称方案数。统计答案的时候先线性筛算出欧拉函数来就好了。

    还有一个就是,$0$元素对子序列和是没有影响的,也就是可以随意选,那么我们计算方案的时候先不考虑$0$,最后所有方案数乘上一个$2^{c_0}$即可。

    接下来考虑怎么DP。

    设$f_s$表示选出元素和为$s$的子序列的方案数,那么有初值$f_0=0,;f_{x}=c_x$。

    然后自然地写出转移方程:$$f_s=sumlimits_{tsubsetneq s}f_{soplus t} imes c_t$$

    这里运用了状压DP枚举子集的方法,枚举$s$只能从真子集关系的数值转移过来,然后$oplus$表示异或,由二项式定理可知转移的时间复杂度是$O(3^d)$。

    这里出现了一个问题,那就是算重了。举个例子,子序列${1,2}$,这种转移方法既会计算元素$1$加入子序列${2}$形成的方案,也会计算元素$2$加入子序列${1}$形成的方案。因此对于一个长度为$i$的子序列,会算出$i!$个重复的方案。呃,注意我们定义的要求的“方案数”,是指“某种总和的优秀子序列个数”,而不是“组成某种总和的优秀子序列个数的路径计数”。别搞错了,前者才是跟答案直接挂钩的。

    有两种解决方法。

    第一种是DP再加一维。设$f_{i,s}$表示选了$i$个元素,元素和为$s$的子序列的方案数,其他都差不多,然后对于相同元素和的子序列方案数有$g_s=sumlimits_{i=1}^{min(d,s)} dfrac{f_{i,s}}{i!}$,但是这样时间复杂度就变成了$O(3^dd)$,期望得分60分。

    第二种就是找出别的转移方法。一般来说是考虑每次加入最大的数,因为我们更新$f_s$是从小到大枚举的,别乱套了。思考一下,发现新加入的数如果是最大的,一定有比当前集合的最高二进制位更大的二进制位,那么它的值就会大于当前集合。欧了,取出$complement_m s$的所有大于$s$的子集$t$加入即可。因为是刷表法,乱写一个式子:

    $$f_s imes sumlimits_{tsubsetneq complement_m s,;t>s}c_t xrightarrow{+}f_{soplus t}$$

    感觉说完了。呃,还是写一下快读吧,养成习惯,这输入已经到了$10^6$级别,影响还是挺大的。还有register优化之类的……

    代码(100分):

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    #include<queue>
    #include<map>
    #include<set>
    #define IL inline
    #define RG register
    #define _1 first
    #define _2 second
    using namespace std;
    typedef unsigned long long LL;
    #define RI RG int
    const int N=1e6;
    const int M=1<<18;
    const LL mod=1e9+7;
    
    IL void read(RI &x){
        x=0;    RG char ch=getchar();
        while(!isdigit(ch))    ch=getchar();
        for(;isdigit(ch);ch=getchar())    x=(x<<3)+(x<<1)+ch-'0';
    }
    
        int n,m,c[M+3];
    
    IL void init(){
        read(n);
        RI maxx=0;
        for(RI i=1,x;i<=n;i++){
            read(x);
            c[x]++;
            maxx=max(maxx,x);
            
        }
        
        for(m=1;m<=maxx;m<<=1);
        
    }
    
        LL f[M+3];
        int k,lis[M+3];
        bool prm[M+3];
        int phi[M+3];
        
    IL void getphi(){
        phi[1]=1;
        for(RI i=2;i<=m;i++){
            if(!prm[i]){
                lis[++k]=i;
                phi[i]=i-1;
                
            }
            
            for(RI j=1;j<=k&&i*lis[j]<=m;j++){
                prm[i*lis[j]]=true;
                
                if(i%lis[j]==0)
                    phi[i*lis[j]]=phi[i]*lis[j];
                else 
                    phi[i*lis[j]]=phi[i]*(lis[j]-1);
                
            }
            
        }
        
    }
    
    IL LL qpow(LL a,LL b){
        LL ans=1;
        for(;b;b>>=1,a=a*a%mod)
        if(b&1)
            ans=ans*a%mod;
        return ans;
        
    }
    
    int main(){
        init();
        
        f[0]=qpow(2,c[0]);
        for(RI s=1;s<m;s++)
            f[s]=c[s];
        for(RI s=1;s<m;s++)
        if(f[s]){
            for(RI x=s^(m-1),t=x;t>s;t=(t-1)&x)
            if(c[t])
                f[s^t]=(f[s^t]+f[s]*c[t]%mod)%mod;
            
            f[s]=f[s]*f[0]%mod;
            
        }
                
        getphi();
        
        RG LL ans=0;
        for(RI i=0;i<m;i++)
        if(f[i])
            ans=(ans+f[i]*phi[i+1]%mod)%mod;
        printf("%lld",ans);
    
        return 0;
    
    }
    View Code
  • 相关阅读:
    字符串逆序输出
    格式化输出
    redis的使用
    redis介绍
    虚拟机间的网络配置+远程访问数据库
    django之contenttype组件
    http请求
    cookie和session
    Django视图解决csrftoken认证
    Django视图解析
  • 原文地址:https://www.cnblogs.com/Hansue/p/12968483.html
Copyright © 2020-2023  润新知