XTUOJ数学专题

1.1242-XTUOJ

Alice经常弄丢钥匙,所以她经常需要去配钥匙,但是锁匠不能保证每一把配的钥匙都能打开。Alice 不想多跑,所以她决定一次让锁匠配多把钥匙来提高成功率。假设每次配钥匙都是独立事件,锁匠有p/100 的概率配好钥匙,请问Alice要达到r/100的概率至少有一把钥匙能打开门,最少需要配多少把钥匙?

输入

第一行是一个整数T(0T11,000) ,表示样例数。 每个样例占一行,为两个整数p,r,0p,r100 

输出

每行输出一个样例的结果。如果不可能达到目的,输出“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经验之谈)