2020牛客寒假集训营3 E题 牛牛的随机数(数位dp)

### 题目链接 ###

题目大意:给定两个数值区间 [\(l\)\(1\)\(r\)\(1\)]和[\(l\)\(2\)\(r\)\(2\)],分别从这两个区间随机拿取一个数,求两数异或值的期望。输出以 \(P*Q\) \(-1\)\((mod\) \(10\)\(9\) \(+\) \(7)\) 下给出,其中 \(P\) 为所有异或之和,\(Q\) 为(\(r\)\(2\)-\(l\)\(2\)+1)\(*\)(\(r\)\(1\)-\(l\)\(1\)+1)。易知求总的异或和即可。


分析:

  • 对于两个数异或做贡献,必须满足的是这两数二进制的第 \(i\) 位上(从二进制末尾开始第一位),一个是 \(0\) 一个是 \(1\) ,他们异或后所产生的贡献为 \(2\)\(i-1\)
    比如 6 和 8 的二进制:
    \(6:0110\)
    \(8:1000\)
    它们异或做的贡献在第 2 位(6上是 1 ,8 上是 0,贡献为 \(2\)\(1\))、第 3 位(6上是 1 ,8 上是 0,贡献为 \(2\)\(2\))以及第 4 位(6上是 1 ,8 上是 0,贡献为 \(2\)\(3\))。故 6 与 8 异或做的贡献是 \(2\)\(1\)\(+\)\(2\)\(2\)\(+\)\(2\)\(3\)\(=14。\)
    所以只要统计二进制的每一位上,第一个区间为 1 的个数与第二个区间为 1 的个数即可。

  • 故设第一个区间所有数中二进制第 \(i\) 位上为 1 的个数为 \(S\)\(1\),第二个区间所有数中二进制第 \(i\) 位上为 1 的个数为 \(S\)\(2\)
    以及,第一个区间数的个数为 \(sum\)\(1\),第二个区间数的个数为 \(sum\)\(2\)
    则二进制第 \(i\) 位上所做的贡献为:\(S\)\(1\)\(*\)(\(sum\)\(2\)\(-\)\(S\)\(2\))\(+\)\(S\)\(2\)\(*\)(\(sum\)\(1\)\(-\)\(S\)\(1\))。

  • 于是我们需要枚举二进制第 \(i\) 位,然后统计出 \(1\) ~ \(r\)\(1\) 中,二进制第 \(i\) 位上是 1 的有多少个数。
    比如统计 \(1\) ~ \(16\) 在二进制第 3 位上是 1 的有多少个数 :

             \(X\) \(X\) \(1\) \(X\) \(X\)
    位数:\(5\)  \(4\)  \(3\)  \(2\)  \(1\)

    从最高位枚举到第 3 位时,第 3 位如果是 1 则有形如 \(0\) \(0\) \(1\) \(X\) \(X\)\(0\) \(1\) \(1\) \(X\) \(X\)这两种,然后再分别以这两种形式深搜下去,统计能被组成的数的个数之和。
    所以 dfs 需要统计的是,每当 dfs 枚举到前三位中形如 \(X\) \(X\) \(1\) 时,都加上后三位能到达形如\(1\) \(X\) \(X\) 的个数(因为在枚举到第三位\(limit\)有效时,可能有些\(XX1\) 并不能枚举到某些的 \(1XX\),所以并不是简单地相乘),即为答案。

  • 比如这里求 \(1\) ~ \(16\) 中二进制第 3 位上为 1 的数的个数:从最高位枚举到第 3 位是 1 时的会有 \(001、011\) 两种,然后发现 \(001\) 可以到达 \(00100、00101、00110、00111\),由于这里对 \(011\)并没有限制,同样也会有\(4\)种。所以总共有 \(8\) 种。

  • 如果求第 \(i\) 位,注意当数位枚举到第\(i\)位时,如果这一位只能到 0 而到不了 1 (因为 \(limit\)),则不能放进 \(for\) 循环(具体还是要看怎么写的),否则可能会导致枚举这位是 0 时, \(res\) 加上了 \(0\) \(X\) \(X\) 的情况,使得答案变大。

代码如下:

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const ll mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 1e18 + 10;
const double eps = 1e-6;
using namespace std;
int T;
int a[65];
ll dp[65][65];
ll l1,r1,l2,r2;
ll dfs(int pos,bool lead,bool limit,int t){
    if(!pos) return 1ll;
    if(!limit&&!lead&&dp[pos][t]!=-1) return dp[pos][t];
    int up=limit?a[pos]:1;
    ll res=0;
    if(pos==t&&up) res=dfs(pos-1,false,limit,t);
    else if(pos!=t){
        for(int i=0;i<=up;i++){
            if(lead&&i==0) res=(res+dfs(pos-1,true,limit&&i==a[pos],t))%mod;
            else res=(res+dfs(pos-1,false,limit&&i==a[pos],t))%mod;
        }
    }
    if(!limit&&!lead) dp[pos][t]=res;
    return res;
}
ll solve(ll x,int t){
    int pos=0;
    while(x){
        a[++pos]=x&1;
        x/=2ll;
    }
    if(pos<t) return 0ll;
    return dfs(pos,true,true,t);
}
ll qpow(ll x,ll y){
    ll ans=1;
    while(y){
        if(y&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        y/=2ll;
    }
    return ans;
}
int main()
{
    memset(dp,-1,sizeof(dp));
    scanf("%d",&T);
    while(T--){
        scanf("%lld%lld%lld%lld",&l1,&r1,&l2,&r2);
        ll res=0;
        ll s1,s2,s3,s4;
        ll s=(((r1-l1+1ll)%mod)*((r2-l2+1ll)%mod)%mod)%mod;
        s=qpow(s,mod-2ll);
        ll e=1ll;
        for(int i=1;i<=60;i++,e*=2ll){
            e%=mod;
            s1=(solve(r1,i)-solve(l1-1ll,i)+mod)%mod;
            s2=(solve(r2,i)-solve(l2-1ll,i)+mod)%mod;
            s3=(s1*((r2-l2+1ll-s2)%mod))%mod;
            s4=(s2*((r1-l1+1ll-s1)%mod))%mod;
            res=(res+((s3+s4)%mod*e)%mod)%mod;
        }
        printf("%lld\n",(res*s)%mod);
    }
}

/*
2
3 5 7 8
1 3 3 5
*/

猜你喜欢

转载自www.cnblogs.com/Absofuckinglutely/p/12291015.html