1 条题解

  • 0
    @ 2025-8-24 21:28:50

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 王熙文
    **

    搬运于2025-08-24 21:28:49,当前版本为作者最后更新于2023-07-16 11:42:44,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    前言

    偶然做到这题,看到讨论区有一个 hack 将所有的题解都叉掉了,因此就想用我的代码通过这个 hack。经过了两天的优化,代码终于过了,因此来发一篇题解。

    upd 2023.7.17: 修改了一些细节,重新交了一下题解。

    思路

    首先发现题目要求单位分数的个数最少,且直接 dfs 可能会进入一个错解出不来,bfs 会空间超限,所以使用迭代加深搜索,从小到大枚举选的分数个数 cntcnt,并 dfs。

    注意到题目也要求最大的分母最小,且如果限制了分母的最大值有利于剪枝,所以也将最大的分母迭代加深,设限制为 axaxaxax10310^310710^7 每次乘 1010

    可以参考下面这个代码理解一下。代码中 ch 表示当前选的数,ans 表示答案选的数。

    void dfs(int now,int a,int b)
    {
    	if(now==cnt+1)
    	{
    		if(a!=0) return;
    		if(ans[1]==0 || ch[cnt]<ans[cnt])
    		{
    			for(int i=1; i<=cnt; ++i) ans[i]=ch[i];
    		}
    		return;
    	}
    	for(int i=ch[now-1]+1; i<=ax; ++i)
    	{
    		int gta=a*i-b,gtb=b*i,g=gcd(gta,gtb);
    		gta/=g,gtb/=g;
    		ch[now]=i,dfs(now+1,gta,gtb);
    	}
    }
    

    这样并不能通过,考虑优化代码。以下是我加的几个优化:

    • 设接下来选的分数为 1i\dfrac{1}{i},则 ab1i\dfrac{a}{b}\ge\dfrac{1}{i},所以 ibai \ge \dfrac{b}{a}

    • 因为后面选的分数一定小于当前,所以必须满足 (cntnow+1)1iab(cnt-now+1)\cdot \dfrac{1}{i} \ge \dfrac{a}{b} 才有可能满足。因此 i(cntnow+1)bai \le \dfrac{(cnt-now+1)\cdot b}{a}

    • nowcnt1now \le cnt-1 时,需要满足下一次 ii 存在(下一次 ii 的限制区间左端点小于等于右端点)。因为 ii 的限制有关 a,ba,b 的都是两个数的比例(ab\dfrac{a}{b}ba\dfrac{b}{a}),所以在考虑下一次的时候 a,ba,b 不需要约分。设下一次的 a,ba,bnxta,nxtbnxta,nxtb。提出来 nxtbnxtai\dfrac{nxtb}{nxta} \le iiaxi \le ax 这两个条件,得 nxtbnxtaax\dfrac{nxtb}{nxta} \le ax,即 biaibax\dfrac{bi}{ai-b} \le ax。所以 iaxbaxabi \ge \dfrac{axb}{axa-b}

    • 用类似的方法推 nowcnt2now \le cnt-2axbiax(aib)biax\dfrac{axbi}{ax(ai-b)-bi} \le ax)可以得到 iaxbaxa2bi \ge \dfrac{axb}{axa-2b}。因此猜测对于任意的 nownow 都有 iaxbaxa(cntnow)bi \ge \dfrac{axb}{axa-(cnt-now)b}。使用类似的方法可以证明。

    • 假设 cnt=3cnt=3,那么 $\dfrac{a}{b}=\dfrac{1}{x}+\dfrac{1}{y}+\dfrac{1}{z}=\dfrac{xy+xz+yz}{xyz}$。又因为 x,y,zaxx,y,z \le ax,所以若 a>cntax2a>cnt\cdot ax^2b>ax3b > ax^3,则无解。类似的,若在过程中 a>(cntnow+1)axcntnow a > (cnt-now+1)\cdot ax^{cnt-now}b>axcntnow+1b > ax^{cnt-now+1} 则无解。

    • now=cntnow=cnt 时,最后一个分数一定为 ab\dfrac{a}{b},所以直接检查是否合法即可,不需要递归到下一层。

    • now=cnt1now=cnt-1 时,还剩下两个分数。设 $\dfrac{a}{b}=\dfrac{1}{x}+\dfrac{1}{y}=\dfrac{x+y}{xy}$。所以 a,ba,b 同时乘上某个数(设其为 zz)后分别等于 x+y,xyx+y,xy。因为 x+y2axx+y \le 2ax,所以 z2axaz \le \dfrac{2ax}{a},并不会太大。因此当 zz 小于等于某个阈值时,可以直接枚举 zz,并解方程组 {az=x+ybz=xy\begin{cases}az=x+y\\bz=xy\end{cases} 即可得到最后两个分数,检查是否合法即可。

    • 在上面优化的过程中,方程有不相等的两解当且仅当 Δ=(az)24bz>0\Delta=(az)^2-4bz > 0z>4ba2z >\dfrac{4b}{a^2},因此枚举的时候下界为 4ba2+1\lfloor \dfrac{4b}{a^2}\rfloor+1

    使用这些优化后,便可以快速通过讨论区的 hack。

    代码

    代码中使用了 __int128,因为不确定是否会爆 long long

    #include<bits/stdc++.h>
    using namespace std;
    #define int __int128
    int ceil(int a,int b) { return (a+b-1)/b; }
    int gcd(int a,int b) { return b==0?a:gcd(b,a%b); }
    int a,b;
    int cnt,ax;
    int ch[1000010],ans[1000010];
    int ppow[1000010];
    int sum=0;
    void dfs(int now,int a,int b)
    {
        if(now==cnt-1 && min(ax*2/a,ax*ax/b)<=1000)
        {
            int tmp=min(ax*2/a,ax*ax/b);
            for(int i=(4*b/a/a)+1; i<=tmp; ++i)
            {
                int delta=a*i*a*i-4*b*i;
                int sqr=sqrtl((long double)delta)+2;
                while(sqr*sqr>delta) --sqr;
                if(sqr*sqr!=delta || (a*i+sqr)%2!=0) continue;
                ch[cnt-1]=(a*i-sqr)/2,ch[cnt]=(a*i+sqr)/2;
                if(ch[cnt-1]>ch[cnt-2] && ch[cnt]>ch[cnt-1] && ch[cnt]<ax)
                {
                    ax=ch[cnt];
                    for(int i=1; i<=cnt; ++i) ans[i]=ch[i];
                    for(int i=1; i<=cnt; ++i) ppow[i]=min(ppow[i-1]*ax,(int)1e29);
                }
            }
            return;
        }
        if(now==cnt)
        {
            if(b%a!=0 || b/a<=ch[cnt-1]) return;
            ch[cnt]=b/a;
            if(ch[cnt]<ax)
            {
                ax=ch[cnt];
                for(int i=1; i<=cnt; ++i) ans[i]=ch[i];
                for(int i=1; i<=cnt; ++i) ppow[i]=min(ppow[i-1]*ax,(int)1e29);
            }
            return;
        }
        if(a>(cnt-now+1)*ppow[cnt-now] || b>ppow[cnt-now+1]) return;
        int l=max(ch[now-1]+1,max(ceil(b,a),ceil(b*ax,a*ax-(cnt-now)*b))),r=min(ax,(cnt-now+1)*b/a);
        if(now==cnt-1)
        {
            for(int i=l; i<=r; ++i) ch[now]=i,dfs(now+1,a*i-b,b*i);
        }
        else
        {
            for(int i=l; i<=r; ++i)
            {
                ch[now]=i;
                int g=gcd(a*i-b,b*i);
                dfs(now+1,(a*i-b)/g,b*i/g);
            }
        }
    }
    signed main()
    {
        ppow[0]=1;
        long long re; cin>>re; a=re; cin>>re; b=re;
        int g=gcd(a,b); a/=g,b/=g;
        if(a==1) return cout<<(long long)b,0;
        cnt=2;
        while(1)
        {
            ans[cnt]=1e9;
            ax=1e3;
            while(ax<=1e7)
            {
                for(int i=1; i<=cnt; ++i) ppow[i]=min(ppow[i-1]*ax,(int)1e29);
                dfs(1,a,b);
                if(ans[1]!=0 && ans[1]!=1e9)
                {
                    for(int i=1; i<=cnt; ++i) cout<<(long long)ans[i]<<' ';
                    return 0;
                }
                ax*=10;
            }
            ++cnt;
        }
        return 0;
    }
    
    • 1

    信息

    ID
    737
    时间
    1000ms
    内存
    128MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者