平方王国

平方王国

k查找,二分

题意

有 \(n\) 名居民,编号 \(1\) 到 \(n\)。第 \(i\) 名居民的石柱高度为

\(h_i = \left(i + \frac{b}{a}\right)^2\)

每对居民之间有一个梯子,长度为两根柱子高度差的绝对值。
总共有 \(\frac{n(n-1)}{2}\) 个梯子。
求按长度升序排列后第 \(k\) 个梯子的长度,输出为最简分数 \(\frac{p}{q}\)。


关键观察

因为 \(i > j\) 时 \(h_i > h_j\),所以梯子长度可以写成

\(L_{i,j} = h_i - h_j = \left(i + \frac{b}{a}\right)^2 - \left(j + \frac{b}{a}\right)^2\)

展开:

\(L_{i,j} = (i-j)\left(i+j + \frac{2b}{a}\right) = \frac{(i-j)\bigl(a(i+j)+2b\bigr)}{a}\)

所有长度都可以表示成某个整数除以 \(a\)。
所以我们只需要对分子进行二分。


按差值分组

令差值

\(d = i - j\)

其中

\(d = 1,2,\dots,n-1\)

并且对于固定的 \(d\),有

\(i = j + d,\qquad j = 1,2,\dots,n-d\)

此时长度分子为

\(N_{d,j} = d\bigl(a(2j+d)+2b\bigr)\)

整理成关于 \(j\) 的一次函数:

\(N_{d,j} = a d^2 + 2bd + 2ad\,j\)

也可以写成

\(N_{d,j} = \bigl(a d^2 + 2bd\bigr) + 2ad\,j\)

注意 \(j\) 从 \(1\) 开始。

固定 \(d\) 后,\(N_{d,j}\) 是一个等差数列:

  • 首项(当 \(j=1\)):
    \(N_{d,1} = a d^2 + 2ad + 2bd = a d^2 + 2(a+b)d\)
  • 公差:
    \(2ad\)
  • 项数:
    \(n-d\)

二分答案

设我们要找的第 \(k\) 小长度分子为 \(M\)。
定义函数 \(C(M)\) 表示分子不超过 \(M\) 的梯子数量。

如果 \(C(M) \ge k\),说明第 \(k\) 小的长度不超过 \(\frac{M}{a}\)。
因此可以二分最小的 \(M\),使得 \(C(M) \ge k\)。

二分范围:

  • 下界:\(1\)
  • 上界:最大分子出现在 \(i=n,\ j=1\),即 \(d=n-1\):
    \(M_{\max} = (n-1)\bigl(a(n+1)+2b\bigr)\)

检查函数 \(C(M)\)

对于每个差值 \(d = 1,2,\dots,n-1\):

  1. 计算 \(j=0\) 时的基准值(便于公式统一):
    \(\text{base}_d = a d^2 + 2bd\)

  2. 如果连 \(j=1\) 都不满足,即
    \(M < \text{base}_d + 2ad\)
    由于 \(\text{base}_d\) 随 \(d\) 单调递增,后面的 \(d\) 更不可能满足,直接 break。

  3. 否则,满足 \(N_{d,j} \le M\) 的 \(j\) 的个数为
    \(\left\lfloor \frac{M - \text{base}_d}{2ad} \right\rfloor\)
    但 \(j\) 最大只能取到 \(n-d\),所以实际个数为
    \(\min\left(n-d,\ \left\lfloor \frac{M - \text{base}_d}{2ad} \right\rfloor\right)\)

  4. 累加所有 \(d\) 的个数,如果总数 \(\ge k\),则返回真。


约分与输出

二分得到最小的 \(M\) 后,第 \(k\) 小长度为

\(\frac{M}{a}\)

约分:

\(g = \gcd(M, a)\)

\(p = \frac{M}{g},\qquad q = \frac{a}{g}\)

输出 \(p\) 和 \(q\)。


复杂度

  • 二分答案约 \(O(\log M_{\max})\) 次,\(M_{\max}\) 最大约 \(10^{30}\),约 \(100\) 次。
  • 每次检查遍历有效的 \(d\)。由于 \(\text{base}_d\) 随 \(d\) 快速增长,实际有效的 \(d\) 最多到 \(\sqrt{M/a}\) 量级,约为 \(10^6\)。
  • 总复杂度约为 \(O(100 \times 10^6)\),可以接受。

实现细节

  1. 必须使用 __int128
    \(M_{\max}\) 可达 \(10^{30}\),远超 long long 范围。
    所有涉及 \(M\)、base、乘法、最终答案 \(p\) 的变量都要用 __int128。

  2. 手写 __int128 输出
    cout 不能直接输出 __int128,需要自己写函数转成字符串。

  3. 手写 __int128 的 gcd
    标准库 gcd 不一定支持 __int128。

  4. 循环变量 d
    d 可以用 long long,因为有效循环次数很少,不会溢出。

  5. 提前终止
    在检查函数中,一旦累加数量 \(\ge k\) 就立即返回 true,避免不必要的计算。


#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128;

void print_i128(i128 x) {
    if (x == 0) { cout << '0'; return; }
    string s;
    while (x > 0) {
        s.push_back('0' + x % 10);
        x /= 10;
    }
    reverse(s.begin(), s.end());
    cout << s;
}
i128 igcd(i128 a, i128 b) {
    return b ? igcd(b, a % b) : a;
}

void solve() {
    ll n, k, a, b; cin >> n >> k >> a >> b;
    
    auto check = [&](i128 m) -> bool {
        i128 cnt = 0;
        for(ll d = 1; d < n; d ++) {
            i128 base = (i128)(a * d + 2*(i128)b)*d; 
            if(m < base + (i128)2 * a * d) break;
            i128 y = (m - base) / ((i128)2*a*d);
            cnt += min((i128)(n - d), y);
            if(cnt >= k) return true;
        }
        return false;
    };

    i128 left = 1, right = 1e30;
    while(left < right) {
        i128 mid = (right + left) / 2;
        if(check(mid)) right = mid;
        else left = mid + 1;
    }

    i128 g = igcd(left, (i128)a);
    i128 p = M / g, q = (i128)a / g;
    print_i128(p); cout << ' ';
    print_i128(q); cout << '\n';
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; //cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-11-03 21:48  thenrify  阅读(2)  评论(0)    收藏  举报