【NOI OL #3】优秀子序列

题目链接

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

我们发现,每个元素$a_i\le 2\times 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=\sum\limits_{t\subsetneq s}f_{s\oplus t}\times c_t$$

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

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

有两种解决方法。

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

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

$$f_s\times \sum\limits_{t\subsetneq \complement_m s,\;t>s}c_t \xrightarrow{+}f_{s\oplus 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

猜你喜欢

转载自www.cnblogs.com/Hansue/p/12968483.html