题解:P10137 [USACO24JAN] Walking in Manhattan G
Chenyichen0420 · · 题解
思路分析
思路真的非常的清奇,好题。部分参考这篇题解。先提示一下,一种正解是倍增。
这道题的突破口是奶牛转向的条件。从奶牛第一次转向起,如果他转向,那意味着它遇到了一条与他方向不同,正好出入的奇偶性不同不同的道路。
到这里,我们仍然看不到倍增的影子。硬要说有的话,你可以选择找出所有的交叉点的下一个点。但是交叉点的个数是
但是我们容易发现,当奶牛下一次走向同方向的道路时,其奇偶性必然会发生改变。
有了上面的结论,横纵就先对独立了,也就是说我们可以考虑倍增了。
对于每一条边,记录出他的下
因此我们可以考虑先走到下一个路口上,然后倍增快速寻找最后一次能够走到二者均改变的路口。最后考虑一下剩余的距离对应的位移即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, m, r[200005][19], c[200005][19];
vector<int> h, v; char o;
inline int ps(const vector<int>& v, int p) {
return lower_bound(v.begin(), v.end(), p) - v.begin();
}
inline void query(int x, int y, int d) {
int hp = ps(h, y), vp = ps(v, x), a = 0, lp;
if (y != h[hp])
if (d + y > h[hp]) d -= h[hp] - y, a += h[hp] - y;
else return void(cout << x << " " << d + y << endl);
if (x != v[vp])
if (d + x > v[vp])d -= v[vp] - x, a += v[vp] - x;
else return void(cout << x + d << " " << y << endl);
//正确性:题目保证了初始点在道路上,这也就意味着以上两个if至多会进入其中的一个。
for (int k = 18, ad; ~k; k--) {
int nh = r[hp][k], nv = c[vp][k];
if ((ad = v[nv] - v[vp] + h[nh] - h[hp]) > d)continue;
d -= ad, a += ad, hp = nh, vp = nv;
//不管你先走v还是先走h,都一定会走到下一个奇偶性不同的十字路口
//因此,在这一步,可以优先保时复,直接向后跳就完事了
}
//此时已经跳到了最后一次能暴力走的路口,需要直接处理了。
//注意到我们一直在跳路口,现在的位置也是一个路口。
if (a & 1)
//奇数步,向北走
//如果走不到下一个奇偶性变化的点就在当前路
//否则在拐角后的某个位置
if ((v[lp = c[vp][0]] - v[vp] < d) && (d -= v[lp] - v[vp]))
return void(cout << v[lp] << " " << h[hp] + d << endl);
else return void(cout << v[vp] + d << " " << h[hp] << endl);
//类似于向北走的思路
if ((h[lp = r[hp][0]] - h[hp] < d) && (d -= h[lp] - h[hp]))
return void(cout << v[vp] + d << " " << h[lp] << endl);
else return void(cout << v[vp] << " " << h[hp] + d << endl);
}
signed main() {//写在前面:有参考3qdyj2s2的代码
ios::sync_with_stdio(0); cin >> n >> m;
for (int i = 1, p; i <= n; ++i)
(cin >> o >> p, o != 'V' ? h : v).emplace_back(p);
sort(h.begin(), h.end()); sort(v.begin(), v.end());
h.emplace_back(2.1e9), v.emplace_back(2.1e9);
for (int i = 0; i != 19; i++)
r[h.size() - 1][i] = h.size() - 1,//谨防越界
c[v.size() - 1][i] = v.size() - 1;//直接暴力跳回是好方法
for (int i = h.size() - 2; i >= 0; i--) {
r[i][0] = (h[i] + h[i + 1] & 1 ? i + 1 : r[i + 1][0]);
//检验是否 h[i] 与 h[i + 1] 奇偶性不同
//不同就是下一个点作为转折点
//否则就是下一个点的下一个奇偶性不同的点
for (int j = 1; j != 19; j++) r[i][j] = r[r[i][j - 1]][j - 1];
}
for (int i = v.size() - 2; i >= 0; i--) {
c[i][0] = (v[i] + v[i + 1] & 1 ? i + 1 : c[i + 1][0]);
//道理同上
for (int j = 1; j != 19; j++) c[i][j] = c[c[i][j - 1]][j - 1];
}
for (int i = 1, x, y, d; i <= m; ++i)
cin >> x >> y >> d, query(x, y, d);
}//能快速想到这是倍增的题的人也是真的厉害