20200213 SCOI模拟T1(数位dp)

T1 [湖南集训]大新闻

[湖南集训]大新闻
题目描述
记者弄了个大新闻,这个新闻是一个在 [ 0 , n ) [0,n) [0,n) 内等概率随机选择的整数,记其为 x x x 。为了尽可能消除这个大新闻对公众造成的不良印象,我们需要在 [ 0 , n ) [0,n) [0,n) 内找到某一个整数 y y y ,使得 x ⊕ y x⊕y xy 达到最大值。这里 ⊕ ⊕ 代表异或。
问题在于,记者有可能对大新闻进行了加密。情报显示,大新闻没有被加密的概率为 p 。我们决定采取这样的策略:如果大新闻没有被加密,那么我们选出使得 x ⊕ y x⊕y xy 最大的 y y y ;否则,我们在 [ 0 , n ) [0,n) [0,n) 内等概率随机选择一个整数作为 y y y
请求出 x ⊕ y x⊕y xy 的期望值。

输入格式
输入仅包含一行,其中有一个正整数 n n n 和一个实数 p p p ,含义如问题描述中所述。 p p p 至多精确到小数点后六位。

输出格式
输出一行,代表 x ⊕ y x⊕y xy 的期望值。只有当你的输出与标准输出的相对误差不超过 1 0 5 10^5 105 时,你的输出才会被判为正确。建议保留至少六位小数。

样例数据 1
输入 
3 0.5
输出
2.000000

样例数据 2
输入
123456 0.5
输出
98063.674346

备注
【样例解释】
考虑样例一。如果大新闻没有被加密,那么可能的 x x x 与对应的 y y y 的取值如下:
在这里插入图片描述
此时的期望值为 8 / 3 8/3 8/3
如果大新闻被加密了,那么可能的 x x x y y y 的取值如下:在这里插入图片描述
此时的期望值为 12 / 9 = 4 / 3 12/9 = 4/3 12/9=4/3
所以总的期望值为 2 2 2

【数据规模与约定】
所有测试点的数据规模如下:
在这里插入图片描述
对于全部测试数据, 1 ≤ n ≤ 1 0 1 8 1≤n≤10^18 1n1018

思路:
把问题拆成两个子问题,对于加密和没加密分别求解,最后 a n s = p × a n s p = 0 + ( 1 − p ) × a n s p = 1 ans=p\times ans_{p=0}+(1-p)\times ans_{p=1} ans=p×ansp=0+(1p)×ansp=1

考虑加密:
[ 0 , n ) [0,n) [0,n)中任意两数异或求和。
对于 [ 0 , n ) [0,n) [0,n)中的数,统计每一位上 0 0 0 1 1 1出现的次数。对于一位上, 0 0 0 1 1 1与自己异或对答案没有贡献。最高位为第 n n n位,则统计答案为 a n s = ∑ i = 1 n n u m [ i ] [ 0 ] × n u m [ i ] [ 1 ] × 2 i − 1 ans=\sum_{i=1}^{n} num[i][0]\times num[i][1]\times 2^{i-1} ans=i=1nnum[i][0]×num[i][1]×2i1

考虑不加密:
数位dp
我也不知道怎么搞,没写出来,先咕着

咕~~~

代码:

#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define int ull
typedef unsigned long long ull;

inline char ch(){
	static char buf[1<<21],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;
}

inline int in{
	int s=0,f=1;char x;
	for(x=getchar();x<'0'||x>'9';x=getchar())	if(x=='-')	f=-1;
	for( ;x>='0'&&x<='9';x=getchar())	s=(s<<1)+(s<<3)+(x&15);
	return f==1?s:-s;
}

int n;
double p;
double ans1=0,ans2=0;
int sum[100][2];

inline void DP1(){
	if(n==1){
		ans1=0;
		return;
	}	
	int k=1;
	while(k<n)	k<<=1;
	int num=k-1;
	k>>=1;
	int cnt=k;
	ans1=1.0*num*(n-k)+1.0*k*k;
	num>>=1;
	while(k!=1){
		k>>=1;
		num>>=1;
		if((n-1)&k){
			ans1=ans1+1.0*cnt*k+1.0*(cnt>>1)*num;
			cnt>>=1;
		}
		else	ans1=ans1+1.0*(cnt>>1)*k;
	}
	ans1=ans1/n;
	return;
}

inline void DP0(int now,int maxx,int w){
	if(!maxx)	return;
	if(now>=maxx){
		sum[w][0]+=maxx;
		sum[w][1]+=now-maxx+1;
		for(int i=1;i<w;i++){
			sum[i][0]+=maxx>>1;
			sum[i][1]+=maxx>>1;
		}
		DP0(now-maxx,maxx>>1,w-1);
	}
	else{
		sum[w][0]+=now+1;
		DP0(now,maxx>>1,w-1);
	}
	return;
}

signed main(){
	n=in;
	scanf("%lf",&p);
	DP1();
	int k=1,w=1;
	while(k<<1<n)
		k<<=1,w++;
	DP0(n-1,k,w);
	int mul=1;
	for(int i=1;i<=w;i++){
		ans2=ans2+1.0*sum[i][0]*2/n*sum[i][1]/n*mul;
		mul<<=1;
	}
	double ans=ans1*p+ans2*(1-p);
	printf("%0.6lf\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值