1.1242-XTUOJ
Alice经常弄丢钥匙,所以她经常需要去配钥匙,但是锁匠不能保证每一把配的钥匙都能打开。Alice 不想多跑,所以她决定一次让锁匠配多把钥匙来提高成功率。假设每次配钥匙都是独立事件,锁匠有p/100 的概率配好钥匙,请问Alice要达到r/100的概率至少有一把钥匙能打开门,最少需要配多少把钥匙?
第一行是一个整数T(0≤T≤11,000) ,表示样例数。 每个样例占一行,为两个整数p,r,0≤p,r≤100 。
每行输出一个样例的结果。如果不可能达到目的,输出“Impossible”(不要输出引号),否则输出一个整数。
50 90
4
考点:概率论
易错:浮点数的比较,特殊情况讨论。
分析:
审题,理清题意,我们可以在纸上列出这个式子:
设配了x把钥匙,那么有:
1-(1-p/100)^x>=r/100;
题目到这里是不是已经结束了呢?事实告诉楼主:你想多了- -。好的,那么接下来的工作就是讨论这个式子以及求解出x的值。先来讨论p,r的特殊值,0和100;
根据我们以往的做题经验,这个时候我们习惯于把式子化简,然后讨论这个式子。但是,这样看似良好的习惯却阻碍了我们的进步。如果将原式化简为:
x>=lg((100-r)/100)/lg((100-p)/100)
我们发现,无形中我们还增加了一些限制条件,比如分母不能为0等。所以,这样的讨论很容易导致出错。那么我们在原式的基础上开始讨论。
我们把式子稍微移动一下,如下所示:
1-r/100>=(1-p/100)^x;
我们从r、p分别等于0、100开始入手:
r=0 p任意值 x=1(对比题目后改为 r=0 p=0 x=1 p为其他 Impossible)
r=100 (p=100 x=1)(p其他 Impossible)
p=0 (r=0 x=1)(r其他 Impossible)
p=100 (r=0 Impossible) (r其他 x=1)
由于是实际的问题,我们必须考虑现实情况下,分析是否正确。从题目中可以看出,当r=0,p!=0是x为Impossible,更正后如上所示。那么我们把上面的讨论总结一下得到下面的结论(总结能力!!!):
if(r==100&&p!=100) printf("Impossible\n");
else if(r==0&&p!=0) printf("Impossible\n");
else if(p==0&&r!=0) printf("Impossible\n");
其他情况下,p和r为0或100时应该输出1。
现在我们就来分析一般情况下怎么求。
实质上就是暴力遍历下列式子,
1-r/100>=(1-p/100)^x
求出x。对于浮点数,而且有除法的运算,必须注意:
(1)必须用float或者double类型的变量存储数据。
(2)读取float变量:
float a,b;
scanf("%f%f",&a,&b);
读取double类型变量:
double a,b;
scanf("%lf%lf",&a,&b);
输出二者均用pirntf(“%f\n”,a,b);
(3)浮点数如何比较是否相等?
如:
float a,b;
if(fabs(a-b)<1e-15) printf("equal!\n");//两者之差小于某个给定的精度
这里是和整数不同的地方。
(4)在这个题目中,我们不确定x的取值范围,所以我们更喜欢用while去遍历x的值。
接下来,我们就可以根据逻辑写代码啦!
拓展:
其实还有一种更快的方法,就是直接运算。
将式子化简成如下形式:
x>=lg((100-r)/100)/lg((100-p)/p);
分情况讨论,当x为整数,那么实际答案即为这个整数;如果x为小数,那么实际答案为大于这个小数的最小整数。但是实际提交的时候,却一直显示wrong answer,测试了很多数据,楼主还没找出那个错误样例。先把代码分享给大家。
在这里,也和大家分享一个将样例输入到.txtwen件中的方法。
(1)ofstream mycout("d:/file.txt");//定义一个目标的文本文件,系统会自动生成
(2)把代码中的printf("....")改为myout<<....<
(3)输出完毕后myout.close()。//关闭文件
然后利用beyondcompare软件即可比对两者之间的差别。
代码如下:
#include
using namespace std;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
double p,r;
int x;
scanf("%lf%lf",&p,&r);
double p_temp=(100-p)/100;
double r_temp=(100-r)/100;
if(r==100&&p!=100) printf("Impossible\n");
else if(r==0&&p!=0) printf("Impossible\n");
else if(p==0&&r!=0) printf("Impossible\n");
else
{
if(r==100&&p==100) printf("1\n");
else if(r==0&&p==0) printf("1\n");
else if(p==0&&r==0) printf("1\n");
else if(p==100) printf("1\n");
else
{
x=log10(r_temp)/log10(p_temp);
double a=log10(r_temp)/log10(p_temp);
if(x==a) printf("%d\n",x);
else printf("%d\n",x+1);
}
}
}
return 0;
}
AC代码如下:
#include
using namespace std;
double Matrix(double a,int n)//阶乘函数耗时较多
{
double sum=a;
for(int i=1; ip_temp) printf("1\n");
else
{
do
{
if(fabs(r_temp-Matrix(p_temp,s_count))<1e-15) break;
else s_count++;
}while(r_temp
2.XTUOJ-1177
Description
题目描述
一个N*M的网格,从左下角沿格子线走到右上角,只能往右或者往上走,请问有多少种不同的路线?
输入
多个样例,每行包含两个整数N,M(1≤N,M≤33);如果N和M为0表示输入结束。
输出
每个样例输出一行,为路线的数目。
样例输入
1 1
1 2
33 33
0 0
样例输出
2
3
7219428434016265740
考点:排列组合问题,组合数公式
易错:数据存储精度。
思路分析:
画图分析一下可以发现,不管怎么走,我们横向总要走M段路,纵向总要走N条路,要走的步数总数是确定的,翻译一下这个问题:一个N位长的01串,0有个1有m个,问这样的串有多少种?
很自然的我们由特殊到一般可以发现,n个位置,只要0的位置确定,1的位置也是确定的,问题也就转化成了组合问题。这里要注意,组合数公式C(n,m)=n!/(n-m)!*m!容易溢出,建议用公式C[n][m]=C[n-1][m]+C[n-1][m-1]递归来求。
扩展:
如何理解公式C[n][m]=C[n-1][m]+C[n-1][m-1]?
1.杨辉三角形看图得出结论。
2.在n种物品里面拿m种物品,先取一种物品,有两种结果:
(1)收下这个物品,那么就只能从n-1种物品拿m-1种物品。
(2)丢弃这个物品,那么就只能从n-1种物品里面拿m种物品。
所以有:C[n][m]=C[n-1][m]+C[n-1][m-1]
AC代码如下:
#include
using namespace std;
typedef long long ll;
int main()
{
ll result[100][100];
result[0][0]=1;
for(int i=1;i<=70;i++)
{
for(int j=1;j<=70;j++)
{
if(i==j) result[i][j]=1;
else if(j==1) result[i][j]=i;
else if(j==0) result[i][j]=1;
else if(i
3.XTUOJ-1171
Description
题目描述
一个均质硬币抛n次,求不存在连续2次为正面的方案数。
输入
每行一个正整数n,n≤40。如果n为0,表示输入结束,不需要处理。
输出
每行输出一个结果,为一个整数。
样例输入
1
2
3
0
样例输出
2
3
5
考点:递推思维。
易错:思路易错。
分析:
拿到题目以后,首先分析,第一眼看过去没有思路,再看一遍,还是没有思路- -。
好,这时候我们该用到一种思路叫做“特殊到一般”,我们开始举例子,带入数据找规律,结果发现,感觉毫无规律可循啊!
是的,其实这道题本质上是考我们是否有递归的思维。下面我们代入递归的思路分析一下这道题目。
要写递归就要解决一下两个问题:
(1)递归的终止条件是什么。
(2)问题规模是如何一点点缩小的,如f:(n)如何由f(n-1)表示出来?
我们围绕这两个问题来思考,取局部来分析。
当我们抛完最后一个硬币的时候,结果只有两种:
(1)第n枚硬币朝上。
(2)第n枚硬币朝下。
那么总方案数:
总=f_dp[n]+b_dp[n];
那么第n-1枚硬币的情况是怎么样的呢?
那么就有:
f_dp[i]=b_dp[i-1];//第i-1枚硬币朝下
b_dp[i]=b_dp[i-1]+f_dp[i-1];第i-1枚硬币朝上或者朝下都可以。
递推到最后,就有:
f_dp[1]=1;
b_dp[1]=1;
至此,问题就解决了。
AC代码如下:
#include
#include
#include
using namespace std;
int main()
{
int f_dp[42],b_dp[41];
int n;
f_dp[1]=1;
b_dp[1]=1;
for(int i=2;i<=40;i++)
{
f_dp[i]=b_dp[i-1];
b_dp[i]=b_dp[i-1]+f_dp[i-1];
}
while(scanf("%d",&n),n)
{
printf("%d\n",f_dp[n]+b_dp[n]);
}
return 0;
}
4.1194-XTUOJ
题目描述
快递小哥每天都辛苦的送快递,今天他需要送N份快递给N个收件人,第i份快递需要送给第i个收件人。 请问其中发生恰好K个送错了的情况数是多少?
输入
存在多样例。 每行输入两个整数N和K,1≤N≤1000,0≤K≤N。 如果两个都为0,则表示输入结束,这个样例不需要处理。
输出
每行输出一个样例的结果,因为数值会比较大,所有结果需要对109+7取模。
样例输入
1 1
2 1
3 2
1000 1000
0 0
样例输出
0
0
3
37043040
考点:
排列组合公式,错排法
易错:
(1)输入时,两个都为0才算结束。
(2)数据可能溢出,要在可能会溢出的地方取模。
(3)二维数组不能再Main()函数内部开太大,如 :int num[1000][1000],如果要开这么大的二维数组,必须在main()函数外面定义。
思路:
仔细思考一下就会发现,实质上就是组和加上错排公式,带入计算即可。
扩展:
(1)如何理解错排公式?
错排公式其实也是递归思路的一种,这里我们还是用到了局部分析法:
假设我们有一个函数已经解决了这个问题,令其为f()。
f(n)的含义是错排n封信得到的方案数量。
局部分析:第一步,取第n封信,送到位置k,就有n-1种情况,第二步对位置k的元素进行分析,如果位置k的元素送到了位置n,那么接下来还有f(n-2)中情况,如果没有送到位置n,那么接下来还有f(n-1)种情况。
综上,有:f(n)=(f(n-1)+f(n-2))*(n-1);
还有不理解的朋友可以带数据分析一下~
(2)阶乘函数
typedef long long ll;
ll fact(ll n)
{
if(n==1) return 1;
return n*fact(n-1);
}
AC代码如下:
#include
#include
#include
using namespace std;
typedef long long ll;
const int PI=1e9+7;
#define MAX 1010
ll C[MAX][MAX];
ll A[MAX];
int main()
{
ll N,K;
A[1]=0;
A[2]=1;
memset(C,0,sizeof(C));
C[0][0]=1;
for(int i=1;i<=1000;i++)
{
if(i>=3) A[i]=(i-1)*(A[i-1]+A[i-2])%PI;
C[i][i]=1;
C[i][1]=i;
C[i][0]=1;
}
for(int i=2;i<=1000;i++)
for(int j=2;j
5.1237-XTUOJ
题目描述
如果n和n+2都是素数,我们称其为孪生素数,比如3和5,5和7都是孪生素数。 给你一个区间[a,b],请问期间有多少对孪生素数?
输入
第一行是一个整数K(K≤ 10000),表示样例的个数。 以后每行一个样例,为两个整数,a和b,1≤a≤b≤5000000。
输出
每行输出一个样例的结果。
样例输入
5
1 3
1 10
1 100
1 1000
1 5000000
样例输出
0
2
8
35
32463
考点:素数打表法。
易错:超时,以及区间任意。
思路:题意十分明显,要我们找素数。暴力求解很明显超时,那么我们这里介绍一种新的方法叫做素数打表法。原理:素数的倍数不是素数。
我们找到1-5000000中的所有素数即可。不过要注意,找到所有素数后还需要优化,提供两种优化思路:
(1)先把结果算出来存储到指定结构中,最后输出。
(2)改变算法和数据结构,这里需要深入题目分析,举例来寻找哪些操作是重复的,然后进行优化。
AC代码如下:
#include
#include
using namespace std;
#define MAX 5000002
bool ans[MAX];
int num[MAX];
int main()
{
int K;
memset(num,0,sizeof(num));
memset(ans,false,sizeof(ans));
ans[1]=true;
ans[2]=false;
ans[3]=false;
for(int i=2;i*i<=5000000;i++)
{
if(!ans[i])
{
for(int j=i+i;j<=5000000;j+=i) ans[j]=true;//素数打表
}
}
for(int i=4;i<=5000000;i++)
{
if(i%2==0) num[i]=num[i-1];
else
{
if(!ans[i-2]&&!ans[i]) num[i]=num[i-2]+1;
else num[i]=num[i-2];
}
}
scanf("%d",&K);
while(K--)
{
int a,b;
scanf("%d%d",&a,&b);
if(a%2==0) a++;
printf("%d\n",num[b]-num[a]);
}
return 0;
}
你可能感兴趣的:(ACM经验之谈)