题目链接:http://icpc.ahu.edu.cn/OJ/Problem.aspx?id=600
题目描述:
Description
cxlove对数字情有独钟,最近又开始玩一个有趣的数字游戏。
首先我们定义一种Lucky number:最高位为1的数字(10进制)。
接下来,会给你n个区间,[Li,Ri]
随机从每一个区间内取出1个整数。
问取出的这n个数中至少有K%是Lucky number的概率是多少。
Input
一个整数 T,表示T组数据。(1<=T<=50)
一个整数n,表示区间的个数 (1<=n<=1000)
接下来n行,每一行两个整数Li,Ri表示区间[Li,Ri],并且保证(1<=Li<=Ri<=10^18)
最后一行是一个整数k (0<=k<=100)
Output
一个实数,表示至少有k%是Lucky Number的概率,小数点后保留6位。
Sample Input
Original | Transformed |
2
1
1 2
50
2
1 2
9 11
50
Sample Output
Original | Transformed |
0.500000
0.833333
Source
安徽大学第五届ACM/ICPC程序设计竞赛 现场赛
解题思路:转自http://blog.csdn.net/j_sure/article/details/41624065
警示1:整数的n次方,再也不要用自带的pow()函数。自己写!心情好还能写个快速幂。因为自带的pow()函数是double类型的,精度损失非常大
警示2:区间右端点减去区间左端点,左端点要先减去1。防止左端点被减掉。
警示3:一定要根据dp数组的状态描述仔细地进行初始化。
设dp[i][j]表示前i个区间选到了j个幸运数字的概率。
那么dp[0][0] = 1(0个区间一定是0个幸运数字)
设每个区间选择到幸运数字的概率是v[i]
状态转移方程为:
dp[i][j] = dp[i-1][j-1] * v[] + dp[i-1][j] * (1-v[])
要么是第i个区间选择到了幸运数字,要么是前i-1个区间已经选够了j个幸运数字。
由此我们需要初始化dp[i][0]。因为1个幸运数字都没有选到,所以一路乘即可。
得到至少k%个幸运数字其实就是n * k%想上取整而已。设至少p个。之后把dp[][p] + dp[][p+1] + ... + dp[][n]即可。
现在剩下的问题是预处理区间内幸运数字的个数。这样就可以尽快查询到。(10^18很大啊)。
0~9:1个
10~99:10个
100~999:100个
......
取出左右端点将它们“剪裁”成上述片段即可。这里有用到前缀和。
AC代码:
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
using namespace std;
long long int T,n,k,num;
double dp[1005][1005];
double rate[1005];
long long int l,r;
long long int ll[20];//ll[i]代表最长i位数字所拥有的以1开头的数字的个数
long long int power(long long int x,long long int y)//x^y
{
long long int j=1;
while(y--)
{
j*=x;
}
return j;
}
void Init()
{
ll[0]=0;
for(long long int i=1;i<=18;i++)
{
ll[i]=power(10,i-1);
ll[i]+=ll[i-1];
}
}
long long int getnum(long long int x)
{
char s[25];
sprintf(s,"%lld",x);
int len=strlen(s);
if(len==1)
{
return x?1:0;
}
if(s[0]=='1')
return x-power(10,len-1)+1+ll[len-1];
else
return ll[len];
}
//用这个来求解1开头的数字的个数会超时
/*long long int zg(long long int k)
{
long long int p=k;
long long int w=0,q=1;
while(p)
{
w++;
p/=10;
}
for(long long int i=1;i<=w-1;i++)
q*=10;
if(k/q==1)
return 1;
else
return 0;
}*/
int main()
{
Init();
scanf("%lld",&T);
while(T--)
{
//memset(rate,0,sizeof(rate));
scanf("%lld",&n);
for(long long int i=1;i<=n;i++)
{
scanf("%lld%lld",&l,&r);
num=getnum(r)-getnum(l-1);
rate[i]=1.0*num/(r-l+1);
}
scanf("%lld",&k);
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(long long int i=1;i<=n;i++)
dp[i][0]=dp[i-1][0]*(1.0-rate[i]);
for(long long int i=1;i<=n;i++)
{
for(long long int j=1;j<=i;j++)
{
dp[i][j]+=dp[i-1][j-1]*rate[i]+dp[i-1][j]*(1.0-rate[i]);
}
}
double ans=0.0;
k=ceil(1.0*k/100.0*n);//ceil函数是求大于等于本身的最小整数
for(long long int i=k;i<=n;i++)
ans+=dp[n][i];
printf("%.6f\n",ans);
}
return 0;
}