2020牛客寒假算法基础集训营3.E.牛牛的随机数(数位dp拆位算贡献)

题目链接

题解思路:首先观察题目需要我们求的是什么——期望,那我们其实只要算出每一项的贡献,并把他们加起来,最后再除去总数即可。那么这么大的数据范围怎么算每一项的贡献呢?这里就需要用到数位dp了。由于题目要求的是异或值的期望,因此二进制的数位dp是最好的选择,我们只需要将其拆位就能得到每一位是1或是0的数量。最终答案其实也就是∑(cnta0*cntb1+cnta1*cntb0)*(1<<i),这里面的cnta0代表的是a区间,第i位为0的数量,以此类推,答案就很显然了。


 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
#define ls l,mid,rt<<1
#define rs mid+1,r,rt<<1|1
#define endl '\n'
const int MAXN = 1e6+10;
const double EPS = 1e-12;
const ll mod = 1e9+7;

int T;
ll l1,r1,l2,r2;
ll dp[70][70][2],a[70];

ll dfs(int pos,int sta,int k,bool limit){
    if(pos==0)return sta;
    if(!limit&&dp[pos][k][sta]!=-1)return dp[pos][k][sta];
    int up=limit ? a[pos] : 1;
    ll ans=0;
    for(int i=0;i<=up;i++)
        ans+=dfs(pos-1,sta||(pos==k&&i!=0),k,limit&&i==a[pos]);
    if(!limit)dp[pos][k][sta]=ans;
    return ans;
}

ll solve(ll x,int k){
    int pos=0;
    while(x){
        a[++pos]=x%2;
        x/=2;
    }
    return dfs(pos,0,k,1);
}

ll poww(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return ans;
}

int main()
{
    scanf("%d",&T);
    while(T--){
        memset(dp,-1,sizeof(dp));
        scanf("%lld %lld %lld %lld",&l1,&r1,&l2,&r2);
        ll now=1,ans=0;
        for(int i=1;i<70;i++){
            ll ca0=solve(r1,i)-solve(l1-1,i),ca1=r1-l1+1-ca0;
            ll cb0=solve(r2,i)-solve(l2-1,i),cb1=r2-l2+1-cb0;
            ca1%=mod,ca0%=mod,cb1%=mod,cb0%=mod;
            ans=(ans+(ca1*cb0%mod+ca0*cb1%mod)%mod*now%mod)%mod;
            now=now*2%mod;
        }
        ll x=(r1-l1+1)*(r2-l2+1)%mod;
        cout<<ans*poww(x,mod-2)%mod<<endl;
    }
}

 

posted @ 2020-04-06 16:24  Mmasker  阅读(172)  评论(0编辑  收藏  举报