P1763 题解

· · 题解

前言

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

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

思路

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

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

可以参考下面这个代码理解一下。代码中 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);
    }
}

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

使用这些优化后,便可以快速通过讨论区的 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;
}