Luogu P1763埃及分数 题解
Luogu P1763 埃及分数
做法:迭代加深搜索
注意到题目中对于正确解的要求,于是通过对于搜索深度的限制,从而达到避免DFS陷入死循环的目的,使得dep在一次次加深中找到正确的答案。于是我们拥有了一个朴素的 IDDFS 的解法,能在此题获得100pts但是hack数据全TLE的一个结果。
对于字母的解释:dep (所限制的搜索深度),step(已经搜索到的深度),st数组(用于储存这一次搜索所使用的数字),所使用的 a,b 分别为当前剩余的分数的分子与分母。
对于每一次DFS的边界选择:
根据题意: 每一个答案需要满足单调递增,所以对于现在这个枚举的 i 理应满足:
- \(\frac{1}{i} < \frac{a}{b}, i > st[step - 1]\),所以我们有 $ i > min(b/a,st[step - 1])$
- 又因为 \(\frac{1}{i} > \frac{a}{b \times (dep - step + 1)}\),即\(i < \frac{(dep - step + 1) \times b}{a}\)。
- 对于这一步的解释为:由于答案的单调性,所以当前枚举的每一个 i 都会比后面答案分母和的平均数来的小,所以以此作为上界。
- 就这样我们确定了枚举的上下界,\(i \in (min(a/b,st[step - 1]),\frac{(dep - step + 1) * b}{a})\)
- 而如果在之前的搜索已经找到了答案,则我们可以通过之前答案的 ans[dep] 来确定 i 的上界,当然这是在原本的上界大于 ans[dep] 的情况下成立的,因为答案的单调性,所以这样能避免不必要的搜索,算是一种剪枝。
void iddfs(ll a, ll b, int step) //迭代加深搜索
{
if (step > dep) // 超过深度后返回
return ;
if (b > st[step - 1] && a == 1) // a = 1时,说明找到了正确答案
{
if (ans[step] > b || !flag) // 选择最优的答案
{
flag = 1;
st[step] = b;
for (int i = 1; i <= dep; i++)
ans[i] = st[i]; // 用st数组记录这一次搜索的答案,如果符合需求就存入ans中
}
return ;
}
ll l = max(st[step - 1] + 1, b / a), r = (dep - step + 1) * b / a; // 确定上下界
if (flag && r >= ans[dep])
r = ans[dep] - 1;
for (ll i = l; i < r; i++)
{
st[step] = i;
ll gc = __gcd(a * i - b, b * i);
dfs((a * i - b) / gc, (b * i) / gc, step + 1); // a/b - 1/i
}
}
void solve()
{
cin >> a >> b;
ll c = __gcd(a, b);
a /= c, b /= c;
for (dep = 1; dep <= 10; dep++) // 这里的dep可以很大,因为在找到答案后程序就结束了。
{
iddfs(a, b, 1);
if (flag)
{
for (int i = 1; i <= dep; i++)
cout << ans[i] << " ";
return ;
}
}
}
巧妙的优化:
在获得了100pts但是评测状态Unaccepted后,我们很困难的想出来一种及其巧妙的优化。很容易想到,每次IDDFS的时候时间耗费最大的总是在找到答案的前两层,也就是第 dep - 1 层和第 dep 层,所以考虑对这两层进行优化。
-
注意到在最后两层的时候存在两个解 x,y 使得:$ \frac{a}{b}=\frac{1}{x}+\frac{1}{y}=\frac{x+y}{x\times y} $ 存在。
-
因为 \(\frac{a}{b}\) 一定是分子分母最大公约数为1的分数,所以就会存在一个 k 使得\(\left \{ \begin{array}ak=x+y \\ bk=x\times y \end{array} \right.\) 成立。
-
根据韦达定理,能得到一个方程为:\(t^2 - akt + bk = 0\)。
-
此时\(\Delta =(ak)^2-4bk > 0\),所以$ k>\frac{4b}{a^2} $。
此时我们设 maxb 为分母的最大值,\(y\le maxb \le1\times 10^7\)。
-
因为\(y>x\),所以:
\(x \times y=bk \le maxb(maxb-1)\),所以\(k \le \frac{maxb(maxb-1)}{b}\)
\(x+y = ak \le 2 \times maxb\),所以\(k \le \frac{2 \times maxb}{a}\)
-
因此 \(k \in (\frac{4b}{a^2},min(\frac{maxb(maxb - 1)}{b},\frac{2 \times maxb}{a})]\)
-
所以在确定了 k 的范围后,我们可以通过枚举 k 替代原本的DFS过程,这显然对于时间是很不错的一个优化。
对于x和y的求解:
不难通过小学二年级学过的求根公式列出:
- \(\begin{cases}x=\frac{ak - \sqrt\Delta}{2}\\y=\frac{ak + \sqrt\Delta}{2}\end{cases}\)
- 因为\((ak)^2与ak\)的奇偶性相同,而且\(-4bk不影响\Delta\)的奇偶性,所以如果\(\Delta\)是完全平方数,会与\(ak\)的奇偶性相同,因此只需要保证\(\Delta\)为完全平方数就可以保证\(x和y\)都为整数。
最后的优化
如果 ans[dep] 不为 0,则 maxb 的值可以是 ans[dep],这样会比原先的\(maxb=1 \times 10^7\) 节省很多时间。
最后如下贴出优化的代码:
if (step + 1 == dep && dep >= 2)
{
for (ll k = 4 * b / a / a + 1; k <= min(2 * maxb / a, maxb * (maxb - 1) / b); k++)
{
ll delta = k * k * a * a - 4 * k * b, s = sqrt(delta), x, y;
if (s * s != delta || delta < 0)
continue;
x = (k * a - s) >> 1, y = (k * a + s) >> 1;
if (x < 0 || y < 0)
continue;
if (y > x)
{
if (!flag || ans[dep] > y)
{
flag = 1;
st[step] = x, st[step + 1] = y;
for (int i = 1; i <= dep; i++)
ans[i] = st[i];
break;
}
}
}
return;
}
这姑且算是我的第一道紫题,故而写下我的第一篇题解作为纪念。
2025/7/29 22:39

浙公网安备 33010602011771号