【数位DP】树状数组

题解

考虑如何计算 f[l][r] f [ l ] [ r ]

很显然,可以分别计算 l,r l , r 的二进制中1的个数,然后减去(最大公共前缀的1的个数)*2。

考虑如何统计每一对 l,r l , r 的最大前缀中1的个数,可以用数位DP。

f[i][j][1/0][1/0] f [ i ] [ j ] [ 1 / 0 ] [ 1 / 0 ] 表示前 i i 位,最大公共前缀中1的个数为j l l 是/否等于r r r 是/否等于n的方案数。这样,所有数对的最大公共前缀中1的个数 t t 显然就是(jf[len][j][0][1]+f[len][j][0][0]),其中 len l e n 表示n的位数。

在转移的时候注意 l l 不能超过r r r 不能超过n即可。

要计算所有数对中二进制中1的个数也可以用类似的方式。

最后, nl=1nr=lf(l,r)=n1t ∑ l = 1 n ∑ r = l n f ( l , r ) = n ∗ ∑ 二 进 制 中 1 的 个 数 − t

时间复杂度: O(Tlog2n) O ( T ∗ l o g 2 n )

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define tt 1000000007
#define maxn 66
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline LL _read(){
    char ch=nc();LL sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
int T,a[maxn];
LL n,nn,f[maxn][maxn][2][2],g[maxn][maxn][2];//f[i][j][1/0][1/0]:前i位,最长公共前缀中有j个1,L是否等于R,R是否等于n的方案数
                                             //g[i][j][1/0]:前i位,有j个1,是否等于n的方案数
int main(){
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    T=_read();
    while(T--){
        n=nn=_read();memset(f,0,sizeof(f));memset(g,0,sizeof(g));
        int len=0;f[0][0][1][1]=1;g[0][0][1]=1;
        while(nn)a[++len]=nn%2,nn/=2;
        for(int i=1;i<=len>>1;i++)swap(a[i],a[len-i+1]);
        for(int i=0;i<len;i++)
            for(int j=0;j<=i;j++)
                if(a[i+1]==1){
                    (f[i+1][j+1][1][1]+=f[i][j][1][1])%=tt,(f[i+1][j][0][1]+=f[i][j][1][1])%=tt,(f[i+1][j][1][0]+=f[i][j][1][1])%=tt;
                    (f[i+1][j+1][1][0]+=f[i][j][1][0])%=tt,(f[i+1][j][1][0]+=f[i][j][1][0])%=tt,(f[i+1][j][0][0]+=f[i][j][1][0])%=tt;
                    (f[i+1][j][0][1]+=f[i][j][0][1]*2%tt)%=tt,(f[i+1][j][0][0]+=f[i][j][0][1]*2%tt)%=tt;
                    (f[i+1][j][0][0]+=f[i][j][0][0]*4%tt)%=tt;
                }else{
                    (f[i+1][j][1][1]+=f[i][j][1][1])%=tt;
                    (f[i+1][j+1][1][0]+=f[i][j][1][0])%=tt,(f[i+1][j][1][0]+=f[i][j][1][0])%=tt,(f[i+1][j][0][0]+=f[i][j][1][0])%=tt;
                    (f[i+1][j][0][1]+=f[i][j][0][1]*2%tt)%=tt;
                    (f[i+1][j][0][0]+=f[i][j][0][0]*4%tt)%=tt;
                }
        for(int i=0;i<len;i++)
            for(int j=0;j<=i;j++)
                if(a[i+1]==1){
                    (g[i+1][j+1][1]+=g[i][j][1])%=tt,(g[i+1][j][0]+=g[i][j][1])%=tt;
                    (g[i+1][j+1][0]+=g[i][j][0])%=tt,(g[i+1][j][0]+=g[i][j][0])%=tt;
                }else{
                    (g[i+1][j][1]+=g[i][j][1])%=tt;
                    (g[i+1][j+1][0]+=g[i][j][0])%=tt,(g[i+1][j][0]+=g[i][j][0])%=tt;
                }
        LL ans=0,sum=0;
        //for(int j=1;j<=len;j++)printf("%d %d %d %d\n",f[len][j][0][0],f[len][j][0][1],f[len][j][1][0],f[len][j][1][1]);
        for(int i=1;i<=len;i++)(ans+=(f[len][i][0][1]+f[len][i][0][0])%tt*i%tt)%=tt;
        for(int i=1;i<=len;i++)(sum+=(g[len][i][0]+g[len][i][1])*i%tt)%=tt;
        printf("%lld\n",((sum*(n%tt)%tt-ans*2%tt)%tt+tt)%tt);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值