【水题选做】一些简单的数学题

P1487失落的成绩单

题意

就是一个\(n\)项数列\(A\)满足\(A_i=\frac{A_{i-1}-A_{i+1}}{2}+d\),给出\(A_1\)\(A_n\),求某一项\(A_m\)的值。

思路

这个式子看起来很好,可惜并不能找到很好的性质,也没有几何意义啥的。

然后我们把它移个项,整理一下,\(A_{i+1}+2A_i-A_{i-1}=2d\)

这是一个常系数线性递推方程,解下特征方程就知道两个特征根分别是\(x=-1\pm\sqrt{2}\),特解也很好求,瞪眼法就知道是\(A_i=d\)

然后\(A_i\)的通解可以表示为\(C_1*(-1+\sqrt{2})^n+C_2*(-1-\sqrt{2})^n+d\)

代入两个点值,把\(C_1\)\(C_2\)解出来就结束了。

这题的坑点在于\(m\)可能\(=0\),要特判输出\(0\)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cmath>
#define db double
using namespace std;
int main(){
    int n,m,i,j;
    db d,A1,An;
    db C1,C2;
    db k1,k2,k3,k4,b1,b2;
    scanf("%d%d",&n,&m);
    scanf("%lf%lf%lf",&d,&A1,&An);
    if(m==0){
        printf("0.000");
        return 0;
    }
    k1=sqrt(2)-1,k2=-1-sqrt(2);
    k3=pow(k1,n),k4=pow(k2,n);
    b1=A1-d,b2=An-d;
    C2=(b2*k1-b1*k3)/(k1*k4-k2*k3);
    C1=(b1-C2*k2)/k1;
    db ans=C1*pow(k1,m)+C2*pow(k2,m)+d;
    printf("%.3lf",ans);
    // system("pause");
    return 0;
}

P1409 骰子

题意

\(n\)个人排成一排,你排在第\(m\)个。

每轮队首的人投一次骰子。

\(\frac{1}{6}\)的概率,队首的人获胜。
\(\frac{1}{2}\)的概率,队首的人排到队尾。
\(\frac{1}{3}\)的概率,队首的人出队。

若队列中仅剩一人,则该人获胜,求你获胜的概率。

思路

数据范围\(n\leq 1000\),看起来就很像一个\(\Theta(n^2)\)的概率DP。

\(i\)个人排第\(j\)个赢的概率为\(dp[i][j]\),那么当\(j>1\)时有\(dp[i][j]=\frac{1}{2} dp[i][j-1]+\frac{1}{3}dp[i-1][j-1]\)

\(j==1\)时有\(dp[i][1]=\frac{1}{6}+\frac{1}{2}dp[i][i]\)

我们直接把dp[i][i]当成未知量,这样每个值就可以用\(k_1dp[i][i]+k_2\)表示,到最后会得到一个关于\(dp[i][i]\)的一元一次方程。

解出\(dp[i][i]\),再代回\(dp[i][1]...dp[i][i-1]\),就得到了\(n==i\)时一整行的结果。

输出\(dp[n][m]\)即可。为了实现方便,我直接把数对\(k_1,k_2\)存到dp数组里。

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#define db double
#define maxn 1010
using namespace std;
struct data{
    db x,y;
    data(){x=y=0;}
    data(db t1,db t2){
        x=t1;y=t2;
    }
    data operator *(db t){
        return data(t*x,t*y);
    }
    data operator +(data t){
        return data(x+t.x,y+t.y);
    }
} dp[maxn][maxn];
int main(){
    int i,j,n,m;
    db tmp;
    scanf("%d%d",&n,&m);
    dp[1][1]=data(0,1);
    for(i=2;i<=n;++i){
        dp[i][1]=data(0.5,1.0/6.0);
        for(j=2;j<=i;++j){
            dp[i][j]=dp[i][j-1]*(1.0/2.0)+dp[i-1][j-1]*(1.0/3.0);
        }
        tmp=dp[i][i].y/(1-dp[i][i].x);
        dp[i][i]=data(0,tmp);
        for(j=1;j<i;++j){
            db t1,t2;
            t1=dp[i][j].x;t2=dp[i][j].y;
            dp[i][j]=data(0,t1*tmp+t2);
        }
    }
    printf("%.9lf",dp[n][m].y);
    // system("pause");
    return 0;
}

P6028算术

题意

算了太长了不想写-_-

思路

显然这个连乘式需要化简。

\[\prod_{i=1}^{k}\frac{p_i^{a_i+1}-1}{p_i^{a_i+1}-p_i^{a_i}}=\prod_{i-1}^{k}\frac{1-\frac{1}{p_i^{a_i+1}}}{1-\frac{1}{p_i}}=\prod_{i=1}^k\sum_{j=0}^{a_i}\frac{1}{p_i^j} \]

观察一下这个东西,对于每个质因子枚举次幂,很像是在枚举因数。我们再乘个\(n\)发现式子就变成了因数之和。(把连乘号跟求和号展开写就很明显了)

所以\(f(n)=\frac{\sigma_1(n)}{n}\),其中\(\sigma_1(n)=\sum_{d|n} d\),这个式子就很简洁了,但为了求前缀和,我们还是写开。

\(f(n)=\frac{1}{n}\sum_{d|n}d\)\(\sum_{i=1}^n f(n)=\sum_{i=1}^{n}\frac{1}{i}\sum_{d|i} d\)

根据套路,枚举因数变为枚举倍数:

\[\sum_{i=1}^nf(n)=\sum_{d=1}^nd\sum_{i=1}^{\lfloor\frac{n}{i}\rfloor}\frac{1}{id}=\sum_{d=1}^n\sum_{i=1}^{\lfloor\frac{n}{i}\rfloor}\frac{1}{i} \]

注意这个$$\sum_{i=1}^{\lfloor\frac{n}{i}\rfloor}\frac{1}{i}$$

整除分块的形式已经很明显了,因为对于一长串的\(d\),内层求和号的结果不变。

\(H(n)=\sum_{i=1}^{n}\frac{1}{i}\)

这个东西是调和级数的部分和,在\(n\)很大的时候有很好的近似\(H(n)\approx ln(n)+\gamma\)\(\gamma=0.57722\)是欧拉常数。

数据小的时候可以预处理,这样\(H(x)\)就可以\(O(1)\)求解,算上整除分块,时间复杂度为\(O(\sqrt n)\)

代码

点击查看代码
#include<cstdio>
#include<cstdlib>
#include<cmath>
#define db long double
#define ll long long
#define gamma 0.57722
using namespace std;
db f[10000010]={0};
db F(ll n){
    if(n<=10000000) return f[n];
    else return log(n)+gamma;
}
int main(){
    int i,j;
    ll l,r,n;
    db ans=0;
    for(i=1;i<=10000000;++i) f[i]=f[i-1]+(db)1.0/(db)i;
    scanf("%lld",&n);
    for(l=1,r=1;l<=r;l=r+1,r=n/(n/l)){
        ans+=(r-l+1)*F(n/l);
        if(r>=n) break;
    }
    printf("%.10Lf",ans);
    // system("pause");
    return 0;
}
posted @ 2022-05-05 18:46  文艺平衡树  阅读(111)  评论(0编辑  收藏  举报