函数 f(x) = | kx + a | + b,当x = -a/k 时最小,函数图像是一个V字形,有最小值。看到这个形状,我们很容易想到用三分法寻找最小值的位置。然而题目中的F(x) 是多个f(x) 的和,如果要用三分法,就必须保证F(x)在最小值左边单调递减(或平),在右边单调递增(或平),否则可能找到别的谷点(函数图形可以参考W的形状)。

这个证明我暂时没想到,但看到题目的标题是整数域三分,那么不管三七二十一先用再说。

三分法的具体做法是,计算区间[l, r]之间的两个三等分点mid1, mid2。比较 F(mid1) 和 F(mid2) 的大小,然后缩小范围(每次缩小三分之一),让其中较小那个点成为新区域的中点。

若 F(mid1) <= F(mid2),区间[l, mid1, mid2, r]变成[l, mid1, mid2-1]。

若F(mid1) > F(mid2),区间[l, mid1, mid2, r]变成[mid1+1, mid2-1]。

这样就保证了较小的那个点始终在新区域内,符合我们的直觉判断。

#include <iostream>
#include <vector>
using namespace std;

struct fun {
    int k, a, b;
};

long long getSum(vector<fun>& f, long long x) {
    long long ans = 0;
    for (fun& fi : f) {
        ans += abs(fi.k * x + fi.a) + fi.b;
    }
    return ans;
}

int main() {
    int t, n, l, r;
    cin >> t;
    while (t--) {
        cin >> n >> l >> r;
        vector<fun> f(n);
        for (int i = 0; i < n; ++i) {
            cin >> f[i].k >> f[i].a >> f[i].b;
        }
        while (l + 2 <= r) {
            int diff = (r - l) / 3;
            int mid1 = l + diff;
            int mid2 = r - diff;
            long long sum1 = getSum(f, mid1);
            long long sum2 = getSum(f, mid2);
            if (sum1 <= sum2) {
                r = mid2 - 1;
            } else {
                l = mid1 + 1;
            }
        }
        cout << min(getSum(f, l), getSum(f, r)) << endl;
    }
}
// 64 位输出请用 printf("%lld")