Codeforce 1089F Fractions 题解(数论,思维,卡常)

本文介绍一种算法,用于将分数1/n分解为若干个真分数之和,这些真分数的分母必须是n的因数,且分数之和等于1。算法核心在于使用n的最小质因数来构造解,确保输出的分数数量不超过10^5。

题意简述

给定一个nnn,确定一些分数,使得这些分数的分母是nnn的因数,且&lt;n&lt;n<n,并且这些分数都是真分数,这些分数的和+1n=1\frac{1}{n}=1n1=1

数据

输入

只有一行,是一个正整数nnn

输出

如果无解输出一行"NO"(没有引号)
如果有解,先输出"YES"(没有引号),然后输出一个kkk,表示有多少个分数。接下来kkk行,每行两个数,是一个经过化简的分数,第一个是分子,第二个是分母。注意您输出的kkk&lt;=105&lt;=10^5<=105,因为数据保证一定在10510^5105长度内有解。如果您输出的kkk超过了10510^5105,就会被判错(具体是OLEOLEOLE还是WAWAWA不知道,反正都是错了)。

数据

输入
2
输出
NO

输入
6
输出
YES
2
1 2
1 3

思路

这个题也是。。。无话珂说。。。

正式开始。首先我们要换一下思维方式,相当于凑几个分数使得和是n−1n\frac{n-1}{n}nn1,由于要求分母都是bbb的因数,我们不妨将这些分数都通分,就又变成:将n−1n-1n1分成一些不和nnn互质的数之和。

举个栗子:输入666,我们就把555分成222+333,接下来就珂以轻易得到:56=2+36=26+36=13+12\frac{5}{6}=\frac{2+3}{6}=\frac{2}{6}+\frac{3}{6}=\frac{1}{3}+\frac{1}{2}65=62+3=62+63=31+21

这样看起来就珂以跑背包了。。。但是注意到,不和nnn互质的数有很多,基本上和nnn同级,但是nnn1e91e91e9的,然后。。。根本跑不动,直接就炸空间了。。。

虽然跑不动背包,但是这个想法看起来很好,不如我们把它 烤了吃了 优化一下。我们发现,所有不和nnn互质的数至少是nnn的一个质因数的倍数。然后我们珂以把这些不和nnn互质的大数拆成某个质因数的几倍。(比如说输入100100100,其中一个不和100100100互质的数是989898100100100的质因数有2,52,52,5,所以我们就珂以把它拆成494949222)。这样虽然量多了一点,但至少空间能过了。不过仿佛不太能保证kkk10510^5105以内。。。

所以要减少数量。我们会发现,一个大的质数kkk一定珂以用两个小的质数a,ba,ba,b通过叠加凑出来(没有说分数一定要不一样哦~)。

简要证明一下。因为a,b,ka,b,ka,b,k都是质数,所以肯定是两两互质的。然后相当于解方程ax+by=kax+by=kax+by=k,有解的条件是kkkgcd(a,b)gcd(a,b)gcd(a,b)的倍数。由于gcd(a,b)=1gcd(a,b)=1gcd(a,b)=1,所以kkkgcd(a,b)gcd(a,b)gcd(a,b)的倍数,所以肯定是有解的。

有了这个之后,我们考虑用nnn最小的两个质因数去凑所有的答案。

(比如输入n=100n=100n=100,最小的是222555,我们就用222555去凑999999。容易看出一个解:555191919个,222用两个。)

这一步珂以用exgcdexgcdexgcd得到(注意要配正整数,是求正整数解,因为一个分数肯定不能选负数个)。由于我的exgcdexgcdexgcd解这个写的比较奇怪,如果看不懂代码中的calccalccalc函数,请看下面的解释:

a,ba,ba,b均为质数的时候,我们会用exgcdexgcdexgcd求解ax+by=1ax+by=1ax+by=1,把x,yx,yx,y乘以kkk,就珂以得到ax+by=kax+by=kax+by=k的解了。但是显然我们这里要求正整数解,所以我们接下来的一个问题就是要把x,yx,yx,y配成正整数。如果两个都正了,就直接返回;否则不珂能两个都是负的,要不然加起来kkk也是负的了。所以肯定是一个正,一个负。不妨令yyy是正的那个(如果yyy是负的,就换一下x,yx,yx,y,最后返回的时候再换回来,这个好好想一想就明白了)。然后我们会发现,如果x,yx,yx,y是一个满足条件的解,那么x+b,y−ax+b,y-ax+b,ya也是一个满足条件的解(代入一下会发现两个ababab抵消了)。那么,我们只需要将yyy不断的−a-aaxxx不断的+b+b+b,就珂以把x,yx,yx,y配成正的。当然,我们显然不能暴力一个一个加上去,所以我这里用了一个cntcntcnt,表示我们需要加多少次。加多少次么。。。我们肯定不能让yyy给变负的了,所以我们最多就是减到y&lt;ay&lt;ay<a为止(也就是⌊ya⌋\lfloor\frac{y}{a}\rflooray次)为止,此时xxx如果还是负的,我们也就没办法了,因为我们已经尽量多的加了。事实证明没有这样的情况。。。

然后这样我们就求出来了一个正整数解。我们珂能会想:这不是负优化了么?艹nm明明更多了好么?此时我们不要急,回到原来的问题上,然后扪心自问一下:
我们完成了什么?

“设a,ba,ba,bnnn的最小的两个质因数,我们已经能够将n−1n-1n1写成ax+byax+byax+by的形式。”

此时,我们会发现,出题人给我们开了一个大玩笑,我们只要输出axn+byn\color{red}只要输出\frac{ax}{n}+\frac{by}{n}nax+nby即珂。(axaxaxbybyby因为分别有a,ba,ba,b,所以显然和nnn不互质)。这个分数约分之后至少能够约成xn/a+yn/b\frac{x}{n/a}+\frac{y}{n/b}n/ax+n/by的形式,明显是满足条件的。也就是说k&lt;=105k&lt;=10^5k<=105是吓人的,只要k=2k=2k=2就珂以做了!!!

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    int n;
    void exgcd(int a,int b,int &x,int &y)//拓展欧几里得
    {
        if (b==0)
        {
            x=1,y=0;return;
        }
        else
        {
            int x_,y_;
            exgcd(b,a%b,x_,y_);
            x=y_;
            y=x_-a/b*y_;
            return;
        }
    }
    pair<int,int> calc(int a,int b,int k)//解方程ax+by=k,并将结果打到pair里
    {

        int x,y;
        exgcd(a,b,x,y);
        x*=k;
        y*=k;
        if (x>=0 and y>=0)
        {
            return make_pair(x,y);
        }
        bool flag=0;
        if (x>=0 and y<0)
        {
            swap(x,y);
            swap(a,b);
            flag=1;
        }

        int cnt=y/a;
        y=y-cnt*a;
        x=x+cnt*b;
        return flag?make_pair(y,x):make_pair(x,y);
        //上面解释过这个奇怪的写法
    }

    int d[100],p[100];
    void Decompose(int x)
    //这一步一定要保证是O(根号n)的!!!
    //我写了个线性的,加上各种玄学优化才过。。。
    {
        int cnt=0;
        for(int i=2;i*i<=x;++i)
        {
            if (x%i==0)
            {
                ++cnt;
                d[cnt]=i;p[cnt]=0;
                while(x%i==0)
                {
                    ++p[cnt];
                    x/=i;
                }
            }
        }
        if (x!=1)
        {
            ++cnt;
            d[cnt]=x,p[cnt]=1;
        }
        return;
    }
    void putfrac(int a,int b)//约分后输出一个分数
    {
        int g=__gcd(a,b);
        printf("%I64d %I64d\n",a/g,b/g);
    }
    void Solve()
    {
        Decompose(n);
        if (d[2]==0 and p[2]==0)//这说明n没有两个质因数,所以也就无解了。。。
        {
            puts("NO");return;
        }
        int a=d[1],b=d[2];//取最小的两个
        pair<int,int>tmp=calc(a,b,n-1);
        int x=tmp.first,y=tmp.second;//获取解
        puts("YES\n2");
        putfrac(a*x,n);
        putfrac(b*y,n);//输出ax/n和by/n
    }
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        scanf("%I64d",&n);
        Solve();
    }
    #undef int //long long
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值