1 条题解
-
0
思路与算法
先考虑顺时针。令gain[i]=p[i]-d[i],把这组净增油量重复两次并建立64位前缀和P。从第s站出发,走完前t条边后的油量为P[s+t]-P[s]。因此一整圈可行,当且仅当P[s+1..s+n]中的最小值不小于P[s]。
这些查询是连续滑动的长度n窗口,用单调递增前缀下标队列,在O(n)时间内得到每个起点的判定。注意窗口不含出发前的P[s],但包含返回起点时的P[s+n]。
逆时针从i走向i-1时,消耗的是d[i-1],所以逆向净增为p[i]-d[i-1](下标循环)。按n-1至0反转站序后,再执行相同的滑动窗口判定,并映射回原站编号。任一方向可行就输出TAK,否则NIE。程序使用缓冲读写应对百万站和百万行输出。
正确性说明
一次从起点开始的行驶中,每到站先收集该站油量,再消耗对应边距离,故经过t条边后的净油量恰是该段gain之和,也就是前缀差。检查整圈中全部t=1..n的最小差不小于0,等价于全过程从未缺油,含最后返回的那条边。
单调队列始终保留当前长度n窗口内未被更小、更新的前缀淘汰的候选,队首给出准确窗口最小值。反向数组逐项使用实际反向要走的边,反转后与单方向模型完全一致。最后两个方向取或,正好符合题意的顺时针或逆时针任选一种。
复杂度
输入数组、净增数组、结果数组以及每次判定的双倍前缀和、下标队列均初始化O(n)。每个前缀至多入队、出队一次,两个方向总时间仍为O(n),空间O(n)。输入和输出字符总长度也为O(n),缓冲区为固定大小。
易错点
逆向用d[i-1],不是d[i];0号站的上一条边为d[n-1]。两个方向满足其一即可,不能要求同时成功。p可以为0,距离d必须为正;候选资料中距离0的点已整点排除。双倍前缀可接近4*10^15,必须使用long long。原站从1编号,代码从0编号,但按原顺序逐行输出即可。
参考代码(C++11)
#include <bits/stdc++.h> using namespace std; using ll = long long; struct Input { static const int S = 1 << 16; char data[S]; int at = 0, len = 0; int get() { if (at == len) { len = fread(data, 1, S, stdin); at = 0; if (!len) return EOF; } return data[at++]; } ll next() { int c = get(); while (c <= 32 && c != EOF) c = get(); ll x = 0; while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = get(); } return x; } }; struct Output { static const int S = 1 << 16; char data[S]; int at = 0; ~Output() { flush(); } void flush() { fwrite(data, 1, at, stdout); at = 0; } void put(char c) { if (at == S) flush(); data[at++] = c; } void line(bool okay) { const char *s = okay ? "TAK\n" : "NIE\n"; while (*s) put(*s++); } }; int main() { Input in; Output out; int n = in.next(); vector<ll> p(n), d(n), gain(n); for (int i = 0; i < n; ++i) { p[i] = in.next(); d[i] = in.next(); } vector<unsigned char> okay(n); auto check = [&](bool reversed) { for (int j = 0; j < n; ++j) { int i = reversed ? n - 1 - j : j; gain[j] = p[i] - d[reversed ? (i ? i - 1 : n - 1) : i]; } vector<ll> prefix(2 * n + 1); for (int i = 1; i <= n; ++i) prefix[i] = prefix[i - 1] + gain[i - 1]; for (int i = 1; i <= n; ++i) prefix[n + i] = prefix[n] + prefix[i]; vector<int> q(2 * n); int head = 0, tail = 0; for (int i = 1; i < 2 * n; ++i) { while (head < tail && q[head] <= i - n) ++head; while (head < tail && prefix[q[tail - 1]] >= prefix[i]) --tail; q[tail++] = i; if (i >= n) { int start = i - n, index = reversed ? n - 1 - start : start; if (prefix[q[head]] >= prefix[start]) okay[index] = 1; } } }; check(false); check(true); for (int i = 0; i < n; ++i) out.line(okay[i]); }
- 1