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; } }
希望用自己的努力为自己赢得荣誉。