P8584 探索未知 题解

题意

给你 \(n\) 个分数,每个分数后面跟着一个操作符 \(op\) , 如果为 \(1\) 就是加上这个分数,是 \(2\) 就减去。初始时是 \(0\) , 询问 \(n\) 次操作后最后的分数是多少,化成最简分数。

特殊地,如果最后是个整数,直接以整数的形式输出。

思路

模拟

考试的时候一看就想到了 [NOIP2020]排水系统,只不过这个题很良心,保证计算的时候不会出现 $2 \times 10^9 $ 以上的数,所以可以不用写高精或者是用 __int128

  • 首先我们要有最基础的数论知识:

    \(\gcd(a,b)\) 表示 \(a\)\(b\) 的最大公约数,用 \(\text{lcm}(a,b)\) 表示 \(a\)\(b\) 的最小公倍数。

    性质一:\(\gcd(a,b) = \gcd(b,a\% b)\) ,有了这个性质,我们就可以快速求出 \(a\)\(b\) 的最大公约数,至于它的证明,我觉得这篇博客写的就很不错。

    性质二:\(\text{lcm}(a,b) = a \times b / \gcd(a,b)\)

    证明:由唯一分解定理可得

    我们设 \(a = p_1^{a_1} \times p_2^{a_2}\times \dots \times p_m^{a_m}\),

    \(b = p_1^{b_1} \times p_2^{b_2}\times \dots \times p_m^{b_m}(a_i,b_i \ge 0)\)

    \(\therefore\) \(a \times b = p_1^{a_1+b_1}p_2^{a_2+b_2}\dots p_m^{a_m+b_m}\)

    $ = p_1^{\min(a_1,b_1)+\max(a_1,b_1)}\dots p_m^{\min(a_m,b_m)+\max(a_m,b_m)}$

    $ = \gcd(a,b) \times \text{lcm}(a,b)$

    证毕。

    有了这两个性质,我们就可以很好的求出两个数的最大公约数和最小公倍数了。

  • 接下来,模拟即可。可以通过代码来理解。

代码实现

#include<bits/stdc++.h>
#define int long long
#define ll long long
#define next nxt
#define re register
#define il inline

using namespace std;
int max(int x,int y){return x > y ? x : y;}
int min(int x,int y){return x < y ? x : y;}

int n,fz,fm,nfz,nfm;
int op;

il int read()
{
    int f=0,s=0;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) f |= (ch=='-');
    for(; isdigit(ch);ch=getchar()) s = (s<<1) + (s<<3) + (ch^48);
    return f ? -s : s;
}

il int gcd(int a,int b) { return b ? gcd(b,a%b) : a; }
il int lcm(int a,int b) { return a*b/gcd(a,b); }
il int Abs(int x) { return x < 0 ? -x : x; }

signed main()
{
    n = read() - 1;
    fz = read() , fm = read() , op = read();/*首先录入第一个分数*/
    if(op == 2) fz = -fz;/*是减法的话,让分子变为负数*/
    for(re int i=1;i<=n;i++)
    {
        nfz = read() , nfm = read() , op = read();/*输入新分子和分母*/
        if(fz == 0) { if(op == 2) fz = -nfz; else fz = nfz ; fm = nfm; continue; }/*如果原先的数是0,那么直接重新赋值即可*/
        int Lcm = lcm(fm,nfm);/*找到分母的最小公倍数*/
        //cout << Lcm << endl;
        fz *= (Lcm/fm) , nfz *= (Lcm/nfm);/*分子乘上分母变成最小公倍数的倍数*/
        //cout << fz << " " << nfz << " ";
        if(op == 1) fz+=nfz; else fz-=nfz;/*分母相等,可以对分子做加减运算*/
        fm = Lcm;/*分母变成最小公倍数*/
        int Gcd = \gcd(Abs(fz),fm);/*给分数约分,注意分子可能为负数,所以要用绝对值找最大公因数*/
        fz /= Gcd; fm /= Gcd;
    }
    if(fz%fm == 0)/*如果是整数*/
    {
        if(fz < 0)
        printf("-%lld",Abs(fz)/\gcd(Abs(fz),fm));/*小于0加负号*/
        if(fz == 0) printf("0");/*分子是0输出0*/
        if(fz > 0) /*大于不用负号*/
        printf("%lld",fz/\gcd(fz,fm));
    }
    else printf("%lld/%lld",fz,fm);/*分数直接输出*/
    return 0;
}

时间复杂度 \(\Theta(n\log n)\) ,可以通过。

自认为写的很详细了,有什么问题可以在评论区问。

posted @ 2023-05-25 21:48  Bloodstalk  阅读(13)  评论(0编辑  收藏  举报