平方王国
平方王国
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\):
-
计算 \(j=0\) 时的基准值(便于公式统一):
\(\text{base}_d = a d^2 + 2bd\) -
如果连 \(j=1\) 都不满足,即
\(M < \text{base}_d + 2ad\)
由于 \(\text{base}_d\) 随 \(d\) 单调递增,后面的 \(d\) 更不可能满足,直接break。 -
否则,满足 \(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)\) -
累加所有 \(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)\),可以接受。
实现细节
-
必须使用
__int128
\(M_{\max}\) 可达 \(10^{30}\),远超long long范围。
所有涉及 \(M\)、base、乘法、最终答案 \(p\) 的变量都要用__int128。 -
手写
__int128输出
cout不能直接输出__int128,需要自己写函数转成字符串。 -
手写
__int128的 gcd
标准库gcd不一定支持__int128。 -
循环变量
d
d可以用long long,因为有效循环次数很少,不会溢出。 -
提前终止
在检查函数中,一旦累加数量 \(\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;
}

浙公网安备 33010602011771号