1 条题解
-
0
思路与算法
令物理接收距离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