1 条题解

  • 0
    @ 2026-10-27 8:51:00

    思路与算法

    令物理接收距离e=1/100000。原图车向左行驶,时刻t的车身矩形是[S1-Vt,S1+L-Vt]×[0,K];第i个球在(i,H-5t²),仅允许t<T=sqrt(H/5),落地时刻不计。

    记A=H-K。在竖直距离z=max(0,A-5t²)≤e的时刻,可接收的横坐标是区间[S1-Vt-r(t),S1+L-Vt+r(t)],其中r(t)=sqrt(e²-z²)。这些时刻连续、区间端点连续且宽度至少L>0,所以并集也是区间。左端下确界为S1-Vsqrt(H/5)-e,并且开;右端最大值能在落地前达到,所以闭。两侧整数判定单调,可二分合法球号0..n-1的左右边界。

    左界判定不需要浮点开根号:令d=S1-e-i,若d≤0则i在左界右侧;否则精确比较V²H/5>d²,注意严格大于。右界方面,若A≤e,最大右端在初始时刻,用i-S1-L与sqrt(e²-max(A,0)²)平方比较即可。

    若A>e,最早竖直接近的时刻为t0=sqrt((A-e)/5),其接收区间右端R0=S1+L-Vt0。任意后续时刻的右端不超过R0+e,因此R0≤Rmax≤R0+e。i≤R0直接判真,i>R0+e直接判假,均用平方比较;带宽e<1,所以整次整数二分最多一个球需要精判。若车顶时刻tK=sqrt(A/5)已有i≤S1+L-VtK+e,也可直接接收。

    对于剩余球,令a=i-S1-L。a≥0时顶角距离在可能时窗中递增,不能接收。a<0时把所有可能接触限制到全有理窗口l=-a/V,h=(e-a)/V:此时水平超出量在0到e之间。该窗口满足l<t0≤h<tK<T,且两端的距离都严格大于e。距离平方减e²是四次多项式f(t)=25t⁴+(V²-10A)t²+2aVt+a²+A²-e²,在整个[l,h]严格凸,只需求其最小值是否≤0。

    必须先识别最小值恰为0的闭切触,避免二分无限逼近。清分母后写成f=αt⁴+Bt²+Ct+D,α>0。由q=4f-tf'=2Bt²+3Ct+4D可消元:若B=0,公共根候选r=-4D/(3C);否则令U=9αC²-8αBD+2B³、W=12αCD+B²C,公共根满足Ur+W=0。U非零时r=-W/U;U=0而W非零时没有公共根;U=W=0时q判别式0,唯一候选r=-3C/(4B)。逐个验证候选在窗口内且f(r)=f'(r)=0即可确认切触。

    排除零最小值后,把t=l+(h-l)z变成z∈[0,1]上的整数系数多项式G。导数严格递增,按导数符号作dyadic二分。端点值给最小值上界;区间宽乘端点导数绝对值最大值给误差上界,因此min(G(l),G(h))-(h-l)max(|G'(l)|,|G'(h)|)是严格可证的下界。找到任意G≤0即接收,下界>0即不接收。没有人为迭代次数或计算epsilon。

    正确性说明

    落地前球高度正,点到车身矩形距离平方为水平超出量平方与竖直超出量平方之和。固定t时的圆角接收区间因此精确成立。最早可接近到T之前的时间集合连续,区间长度始终正,其并集连通;左端随时间严格递减并仅在T的极限达到,故左界开。A>0时车顶时刻之后右端严格递减,而tK<T;A≤0时始终在车高以内,右端从初始就递减。因此右端最大值在落地前达到,故右界闭。

    A≤0时始终在车高以内,右端显然递减。0<A≤e时,若球在初始右端之外,则a>0,顶角距离平方的二阶导数300t²+2V²-20A≥2-20e>0,初始一阶导数2aV>0,所以顶角阶段距离随后增大;进入车身高度后水平超出量也递增,最大右端确在初始。A>e时,t0水平膨胀量为0,后续车身右端只减小且膨胀量≤e,得到宽度e的快速夹界。未被快速判定的球在t0距离>e;车顶判定失败又说明tK水平超出>e。a≥0时,f'(t0)=2Va+2(V²-10e)t0>0,顶角阶段严格凸,所以不能接收。

    a<0时,水平超出0和e对应有理l,h。由快速判定可知l<t0≤h,车顶判定失败给h<tK。所有可能接触必在[l,h]内,且窗口外不可能因距离等号接收。由A≤5h²+e、h=l+e/V,对任意t≥l可得f''(t)≥200(l-e/(2V))²+2V²-150e²/V²-20e>0,其中V≥1、e=1e-5。因此f在整个有理窗口严格凸,最小值唯一,两端严格正。

    零最小值只能发生在f=f'=0的公共根。任意公共根同时满足q=0。B非零时,把f'对q作一次代数约化,其余式乘B²为Ut+W,故U非零时公共根必为有理候选-W/U;U=0,W非零时无公共根;两者均0且C非零时,W=0给D=-B²/(12α),代入U=0可得q的判别式为0,根仍是唯一有理候选-3C/(4B)。B=0则q为一次式。实际困难窗口a<0,所以C非零;C=0的顶角距离单调已直接处理。候选再验证原f和f',既不遗漏公共根,也不接纳消元产生的伪根。

    导数在窗口内严格递增。若窗口两端导数未包夹0,最小值在正的端点,不接收;否则二分始终包住唯一极小点。根据导数绝对值上界,所用下界不大于真正最小值,故下界正时拒绝正确;某点G≤0时接收也正确。零最小值已被精确识别,其余最小值非零:为负时二分中点最终进入负值邻域,为正时窗口收缩后下界最终为正,算法有限终止。最终二分得到接收区间与整数球号的交集,恰为答案。

    复杂度

    设n个球,整数边界二分进行O(log n)次有理比较,最多一次困难顶角判定。设输入及初始精确系数最大位数为D,困难判定二分J轮,dyadic整数与多项式计算的位数为O(D+J)。初始常数次约分、消元和换元按当前朴素大整数算法的保守界为O(D³);二分循环只作整数加乘,每轮O((D+J)²),总时间可写O(D³ log n+J(D+J)²),空间O(D+J)。J取决于非零最小值离0的远近,精确零切触预先处理保证终止,不补输入小数位数或迭代上限。

    易错点

    原图前沿是S1,车向左,后沿为S1+L;不是S1-L。物理距离e对应圆角矩形,不能把横纵方向同时各放宽e当成方角。落地t=T不接受,所以左界严格;初始时刻和距离恰等于e均允许,所以右界闭。不能只比较车顶时刻后沿加e,略早的圆角接触可能多接一个球。闭切触要精确处理共同零点,不能靠函数变号或固定二分轮数。U=W=0不等于C=0,须处理判别式0的分支并验证原多项式。G和G'作上下界比较时必须保持同一正比例单位,不能把二者分别约去不同公因子。物理e始终精确为1/100000,程序没有额外舍入容差。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    struct cpp_int {
        string s;
        bool neg;
        cpp_int(long long v = 0) : s(to_string(v)), neg(v < 0) {
            if (neg)
                s.erase(s.begin());
        }
        void norm() {
            auto p = s.find_first_not_of('0');
            s = p == string::npos ? "0" : s.substr(p);
            if (s == "0")
                neg = false;
        }
        static int cmpabs(const cpp_int &a, const cpp_int &b) {
            if (a.s.size() != b.s.size())
                return a.s.size() < b.s.size() ? -1 : 1;
            return a.s == b.s ? 0 : a.s < b.s ? -1 : 1;
        }
        friend bool operator<(const cpp_int &a, const cpp_int &b) {
            if (a.neg != b.neg)
                return a.neg;
            int c = cmpabs(a, b);
            return a.neg ? c > 0 : c < 0;
        }
        friend bool operator==(const cpp_int &a, const cpp_int &b) {
            return a.neg == b.neg && a.s == b.s;
        }
        friend bool operator!=(const cpp_int &a, const cpp_int &b) {
            return !(a == b);
        }
        friend bool operator>(const cpp_int &a, const cpp_int &b) {
            return b < a;
        }
        friend bool operator<=(const cpp_int &a, const cpp_int &b) {
            return !(b < a);
        }
        friend bool operator>=(const cpp_int &a, const cpp_int &b) {
            return !(a < b);
        }
        cpp_int operator-() const {
            cpp_int r = *this;
            if (r.s != "0")
                r.neg = !r.neg;
            return r;
        }
        friend cpp_int operator+(const cpp_int &a, const cpp_int &b) {
            cpp_int r;
            r.s.clear();
            int carry = 0;
            if (a.neg == b.neg) {
                int i = (int)a.s.size() - 1, j = (int)b.s.size() - 1;
                while (i >= 0 || j >= 0 || carry) {
                    int v = carry + (i >= 0 ? a.s[i--] - '0' : 0) + (j >= 0 ? b.s[j--] - '0' : 0);
                    r.s += char('0' + v % 10);
                    carry = v / 10;
                }
                r.neg = a.neg;
            } else {
                const cpp_int *x = &a, *y = &b;
                if (cmpabs(a, b) < 0)
                    swap(x, y);
                int i = (int)x->s.size() - 1, j = (int)y->s.size() - 1;
                while (i >= 0) {
                    int v = x->s[i--] - '0' - carry - (j >= 0 ? y->s[j--] - '0' : 0);
                    carry = v < 0;
                    if (carry)
                        v += 10;
                    r.s += char('0' + v);
                }
                r.neg = x->neg;
            }
            reverse(r.s.begin(), r.s.end());
            r.norm();
            return r;
        }
        friend cpp_int operator-(const cpp_int &a, const cpp_int &b) {
            return a + (-b);
        }
        friend cpp_int operator*(const cpp_int &a, int b) {
            cpp_int r;
            r.s.clear();
            long long carry = 0;
            long long z = b;
            if (z < 0)
                z = -z;
            for (int i = (int)a.s.size() - 1; i >= 0; i--) {
                carry += (a.s[i] - '0') * z;
                r.s += char('0' + carry % 10);
                carry /= 10;
            }
            while (carry) {
                r.s += char('0' + carry % 10);
                carry /= 10;
            }
            reverse(r.s.begin(), r.s.end());
            r.neg = a.neg ^ (b < 0);
            r.norm();
            return r;
        }
        friend cpp_int operator/(const cpp_int &a, int b) {
            cpp_int r;
            r.s.clear();
            long long rem = 0, z = b;
            if (z < 0)
                z = -z;
            for (char c : a.s) {
                rem = rem * 10 + c - '0';
                r.s += char('0' + rem / z);
                rem %= z;
            }
            r.neg = a.neg ^ (b < 0);
            r.norm();
            return r;
        }
        cpp_int &operator+=(const cpp_int &b) {
            *this = *this + b;
            return *this;
        }
        cpp_int &operator-=(const cpp_int &b) {
            *this = *this - b;
            return *this;
        }
        cpp_int &operator*=(int b) {
            *this = *this * b;
            return *this;
        }
        friend istream &operator>>(istream &in, cpp_int &a) {
            string v;
            if (in >> v) {
                a.neg = v[0] == '-';
                if (v[0] == '-' || v[0] == '+')
                    v.erase(v.begin());
                a.s = v;
                a.norm();
            }
            return in;
        }
        friend ostream &operator<<(ostream &out, const cpp_int &a) {
            if (a.neg)
                out << '-';
            return out << a.s;
        }
    };
    
    cpp_int fromString(string s) {
        cpp_int a;
        a.neg = !s.empty() && s[0] == '-';
        if (!s.empty() && (s[0] == '-' || s[0] == '+'))
            s.erase(s.begin());
        a.s = s;
        a.norm();
        return a;
    }
    cpp_int absBig(cpp_int a) {
        a.neg = false;
        return a;
    }
    cpp_int pow10(size_t n) {
        cpp_int a;
        a.s = "1" + string(n, '0');
        return a;
    }
    cpp_int operator*(const cpp_int &a, const cpp_int &b) {
        vector<long long> d(a.s.size() + b.s.size());
        for (int i = (int)a.s.size() - 1; i >= 0; --i)
            for (int j = (int)b.s.size() - 1; j >= 0; --j)
                d[i + j + 1] += (a.s[i] - '0') * (b.s[j] - '0');
        for (int i = (int)d.size() - 1; i > 0; --i) {
            d[i - 1] += d[i] / 10;
            d[i] %= 10;
        }
        cpp_int c;
        c.s.clear();
        for (auto x : d)
            c.s += char('0' + x);
        c.neg = a.neg != b.neg;
        c.norm();
        return c;
    }
    pair<cpp_int, cpp_int> divmod(cpp_int a, cpp_int b) {
        bool sign = a.neg != b.neg, negrem = a.neg;
        a.neg = b.neg = false;
        cpp_int q, rem;
        q.s.clear();
        cpp_int mul[10];
        for (int i = 0; i < 10; ++i)
            mul[i] = b * i;
        for (char z : a.s) {
            rem = rem * 10 + (z - '0');
            int lo = 0, hi = 9;
            while (lo < hi) {
                int mi = (lo + hi + 1) / 2;
                if (mul[mi] <= rem)
                    lo = mi;
                else
                    hi = mi - 1;
            }
            q.s += char('0' + lo);
            rem -= mul[lo];
        }
        q.neg = sign;
        q.norm();
        rem.neg = negrem;
        rem.norm();
        return {q, rem};
    }
    cpp_int floorDiv(cpp_int a, const cpp_int &b) {
        auto z = divmod(a, b);
        if (a.neg && z.second != 0)
            z.first -= 1;
        return z.first;
    }
    cpp_int rootBig(const cpp_int &a) {
        cpp_int root, rem;
        string s = a.s;
        if (s.size() % 2)
            s = "0" + s;
        for (size_t i = 0; i < s.size(); i += 2) {
            rem = rem * 100 + (s[i] - '0') * 10 + s[i + 1] - '0';
            int x = 9;
            while ((root * 20 + x) * x > rem)
                --x;
            rem -= (root * 20 + x) * x;
            root = root * 10 + x;
        }
        return root;
    }
    string fixedBig(cpp_int rounded, int digits) {
        bool neg = rounded.neg;
        string s = rounded.s;
        while (s.size() <= (size_t)digits)
            s = "0" + s;
        if (digits)
            s.insert(s.size() - digits, ".");
        return (neg ? "-" : "") + s;
    }
    cpp_int roundedRational(cpp_int a, cpp_int b) {
        bool neg = a.neg != b.neg;
        a.neg = b.neg = false;
        auto q = divmod(a, b);
        if (q.second * 2 >= b)
            q.first += 1;
        q.first.neg = neg && q.first != 0;
        return q.first;
    }
    cpp_int gcdBig(cpp_int a, cpp_int b) {
        a = absBig(a);
        b = absBig(b);
        while (b != 0) {
            cpp_int r = divmod(a, b).second;
            a = b;
            b = r;
        }
        return a;
    }
    struct Q {
        cpp_int n, d;
        Q(long long v = 0) : n(v), d(1) {
        }
        Q(cpp_int a, cpp_int b) : n(a), d(b) {
            assert(d != 0);
            if (d < 0) {
                n = -n;
                d = -d;
            }
            cpp_int g = gcdBig(n, d);
            n = divmod(n, g).first;
            d = divmod(d, g).first;
        }
        int sign() const {
            return n == 0 ? 0 : n < 0 ? -1 : 1;
        }
        Q operator-() const {
            return Q(-n, d);
        }
        friend Q operator+(const Q &a, const Q &b) {
            return Q(a.n * b.d + b.n * a.d, a.d * b.d);
        }
        friend Q operator-(const Q &a, const Q &b) {
            return a + (-b);
        }
        friend Q operator*(const Q &a, const Q &b) {
            return Q(a.n * b.n, a.d * b.d);
        }
        friend Q operator/(const Q &a, const Q &b) {
            return Q(a.n * b.d, a.d * b.n);
        }
        friend bool operator<(const Q &a, const Q &b) {
            return a.n * b.d < b.n * a.d;
        }
        friend bool operator<=(const Q &a, const Q &b) {
            return !(b < a);
        }
        friend bool operator>(const Q &a, const Q &b) {
            return b < a;
        }
        friend bool operator>=(const Q &a, const Q &b) {
            return !(a < b);
        }
        friend bool operator==(const Q &a, const Q &b) {
            return a.n == b.n && a.d == b.d;
        }
    };
    Q decimalQ(string s) {
        int exp = 0;
        size_t e = s.find_first_of("eE");
        if (e != string::npos) {
            exp = stoi(s.substr(e + 1));
            s = s.substr(0, e);
        }
        size_t dot = s.find('.');
        int scale = dot == string::npos ? 0 : (int)s.size() - (int)dot - 1;
        if (dot != string::npos)
            s.erase(dot, 1);
        cpp_int a = fromString(s);
        scale -= exp;
        if (scale < 0)
            return Q(a * pow10(-scale), 1);
        return Q(a, pow10(scale));
    }
    using Poly = vector<cpp_int>;
    void trim(Poly &a) {
        while (!a.empty() && a.back() == 0)
            a.pop_back();
    }
    Poly primitive(Poly a) {
        trim(a);
        if (a.empty())
            return a;
        cpp_int g;
        for (const auto &c : a)
            g = gcdBig(g, c);
        if (g != 1)
            for (auto &c : a)
                c = divmod(c, g).first;
        return a;
    }
    Poly integerPoly(const vector<Q> &a) {
        cpp_int D = 1;
        for (const auto &c : a)
            D = divmod(D, gcdBig(D, c.d)).first * c.d;
        Poly p;
        for (const auto &c : a)
            p.push_back(c.n * divmod(D, c.d).first);
        return primitive(p);
    }
    Q evaluate(const vector<Q> &p, const Q &x) {
        Q ans;
        for (int i = (int)p.size() - 1; i >= 0; i--)
            ans = ans * x + p[i];
        return ans;
    }
    cpp_int dyadicValue(const Poly &p, const cpp_int &j, const cpp_int &den) {
        cpp_int ans = p.back(), power = den;
        for (int i = (int)p.size() - 2; i >= 0; i--) {
            ans = ans * j + p[i] * power;
            power = power * den;
        }
        return ans;
    }
    bool convexMinimum(const Q &a, const Q &A, const Q &V, const Q &e) {
        Q Bq = V * V - Q(10) * A, Cq = Q(2) * a * V, Dq = a * a + A * A - e * e;
        Poly P = integerPoly({Dq, Cq, Bq, Q(0), Q(25)}), Pprime;
        for (size_t i = 1; i < P.size(); i++)
            Pprime.push_back(P[i] * (int)i);
        const cpp_int &D = P[0], &C = P[1], &B = P[2], &alpha = P[4];
        if (C == 0)
            return false;
        Q left = -a / V, width = e / V, right = left + width;
        bool candidate = false;
        cpp_int rn, rd;
        if (B == 0) {
            rn = -D * 4;
            rd = C * 3;
            candidate = true;
        } else {
            cpp_int U = alpha * C * C * 9 - alpha * B * D * 8 + B * B * B * 2,
                    W = alpha * C * D * 12 + B * B * C;
            if (U != 0) {
                rn = -W;
                rd = U;
                candidate = true;
            } else if (W == 0) {
                rn = -C * 3;
                rd = B * 4;
                candidate = true;
            }
        }
        if (candidate) {
            if (rd < 0) {
                rn = -rn;
                rd = -rd;
            }
            if (left.n * rd <= rn * left.d && rn * right.d <= right.n * rd &&
                dyadicValue(P, rn, rd) == 0 && dyadicValue(Pprime, rn, rd) == 0)
                return true;
        }
        // Clear the affine substitution t=left+width*z without fraction arithmetic.
        cpp_int scale = divmod(left.d, gcdBig(left.d, width.d)).first * width.d;
        cpp_int shift = left.n * divmod(scale, left.d).first,
                step = width.n * divmod(scale, width.d).first;
        cpp_int powersS[5], powersL[5], powersW[5];
        powersS[0] = powersL[0] = powersW[0] = 1;
        for (int i = 1; i <= 4; i++) {
            powersS[i] = powersS[i - 1] * scale;
            powersL[i] = powersL[i - 1] * shift;
            powersW[i] = powersW[i - 1] * step;
        }
        int choose[5][5] = {{1}, {1, 1}, {1, 2, 1}, {1, 3, 3, 1}, {1, 4, 6, 4, 1}};
        Poly G(5);
        for (int j = 0; j <= 4; j++)
            for (int k = j; k <= 4; k++)
                G[j] += P[k] * choose[k][j] * powersL[k - j] * powersS[4 - k] * powersW[j];
        G = primitive(G);
        Poly Gprime;
        for (size_t i = 1; i < G.size(); i++)
            Gprime.push_back(G[i] * (int)i);
        cpp_int j = 0, den = 1;
        if (dyadicValue(Gprime, 0, 1) >= 0 || dyadicValue(Gprime, 1, 1) <= 0)
            return false;
        for (;;) {
            cpp_int fl = dyadicValue(G, j, den), fh = dyadicValue(G, j + 1, den),
                    dl = absBig(dyadicValue(Gprime, j, den)),
                    dh = absBig(dyadicValue(Gprime, j + 1, den));
            if (fl <= 0 || fh <= 0)
                return true;
            if (min(fl, fh) > max(dl, dh))
                return false;
            cpp_int middle = j * 2 + 1, nextDen = den * 2, fm = dyadicValue(G, middle, nextDen);
            if (fm <= 0)
                return true;
            cpp_int dm = dyadicValue(Gprime, middle, nextDen);
            if (dm == 0)
                return false;
            j = j * 2;
            if (dm < 0)
                j += 1;
            den = nextDen;
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        string s;
        Q H, S, V, L, K;
        cin >> s;
        H = decimalQ(s);
        cin >> s;
        S = decimalQ(s);
        cin >> s;
        V = decimalQ(s);
        cin >> s;
        L = decimalQ(s);
        cin >> s;
        K = decimalQ(s);
        int n;
        cin >> n;
        const Q eps(cpp_int(1), cpp_int(100000));
        Q b = H - K, vv = V * V;
        auto leftOK = [&](int i) {
            Q d = S - eps - Q(i);
            if (d.sign() <= 0)
                return true;
            return vv * H / Q(5) > d * d;
        };
        auto rightOK = [&](int i) {
            Q a = Q(i) - S - L;
            if (b <= eps) {
                Q dy = b.sign() > 0 ? b : Q(0);
                if (a.sign() <= 0)
                    return true;
                return a * a + dy * dy <= eps * eps;
            }
            Q t0sq = (b - eps) / Q(5), rhs = -a;
            if (rhs.sign() >= 0 && vv * t0sq <= rhs * rhs)
                return true;
            rhs = eps - a;
            if (rhs.sign() < 0 || vv * t0sq > rhs * rhs)
                return false;
            Q tksq = b / Q(5);
            if (rhs.sign() >= 0 && vv * tksq <= rhs * rhs)
                return true;
            if (a.sign() >= 0)
                return false;
            return convexMinimum(a, b, V, eps);
        };
        int lo = 0, hi = n;
        while (lo < hi) {
            int m = (lo + hi) / 2;
            if (leftOK(m))
                hi = m;
            else
                lo = m + 1;
        }
        int first = lo;
        lo = 0;
        hi = n;
        while (lo < hi) {
            int m = (lo + hi) / 2;
            if (rightOK(m))
                lo = m + 1;
            else
                hi = m;
        }
        cout << max(0, lo - first) << '\n';
    }
    
    • 1