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

posted @ 2025-07-29 22:59  tree_one  阅读(16)  评论(0)    收藏  举报