Luogu P14255 列车(train) 题解 [ 蓝 ] [ 线段树 ] [ 二维平面转化 ]
感觉自己 DS 学傻了,赛时瞄了一眼口胡了个神秘科技做法并鉴定为不可做题,赛后一说做法是普通线段树秒会。
把限制画在二维平面上是显然的,令横轴为 \(i\),纵轴为 \(j\),则初始有用的部分形成一个倒三角。
考虑分别刻画两种操作:
- 查询操作相当于在平面上查询点 \((x, y)\) 左上角的最小值。
- 修改操作相当于把 \((x, y)\) 右下角的点全部设为极大值。
观察修改操作,发现被设为极大值的部分一定是楼梯形状的。观察查询操作,发现当 \(i\) 确定时,\(j\) 越小 \(p\) 越小,因此维护二维平面是没有必要的,对于每个 \(i\) 维护最小的 \(\bm j\) 即可。这样就把二维平面拍成了一维序列来维护。
考虑用线段树来维护这个序列:
- 修改操作:用
set
维护楼梯的形状,修改之前先在set
上暴力查询修改的是哪部分楼梯,将需要修改的部分在线段树上修改即可。因为每次修改最多只会增加一个段,所以set
上暴力修改均摊复杂度是正确的。线段树具体需要维护的是区间赋值、区间求最小值的操作。 - 查询操作:分为两部分。第一部分是在楼梯内部的区域,这部分直接在线段树上查询。第二部分是在楼梯外部的区域,此时 \(i\) 取最大,\(j\) 取最小一定最优。特判掉即可。
时间复杂度 \(O(n\log n)\)。
#include <bits/stdc++.h>
#define lc (p << 1)
#define rc ((p << 1) | 1)
#define fi first
#define se second
using namespace std;
typedef long long ll;
using pi = pair<int, int>;
const int N = 200005, inf = 0x3f3f3f3f;
int n, m, pos[N];
set<pi> s1, s2;
struct Node{
int l, r;
int mn, tag;
};
struct Segtree{
Node tr[4 * N];
void modify(int p, int v)
{
tr[p].mn = v - pos[tr[p].r];
tr[p].tag = v;
}
void pushup(int p)
{
tr[p].mn = min(tr[lc].mn, tr[rc].mn);
}
void pushdown(int p)
{
if(tr[p].tag)
{
modify(lc, tr[p].tag);
modify(rc, tr[p].tag);
}
tr[p].tag = 0;
}
void build(int p, int ln, int rn)
{
tr[p] = {ln, rn, 0, 0};
if(ln == rn) return;
int mid = (ln + rn) >> 1;
build(lc, ln, mid);
build(rc, mid + 1, rn);
pushup(p);
}
void update(int p, int ln, int rn, int v)
{
if(ln <= tr[p].l && tr[p].r <= rn)
{
modify(p, v);
return;
}
int mid = (tr[p].l + tr[p].r) >> 1;
pushdown(p);
if(ln <= mid) update(lc, ln, rn, v);
if(rn >= mid + 1) update(rc, ln, rn, v);
pushup(p);
}
int query(int p, int ln, int rn)
{
if(ln <= tr[p].l && tr[p].r <= rn) return tr[p].mn;
pushdown(p);
int mid = (tr[p].l + tr[p].r) >> 1;
if(rn <= mid) return query(lc, ln, rn);
if(ln >= mid + 1) return query(rc, ln, rn);
return min(query(lc, ln, rn), query(rc, ln, rn));
}
}tr1;
void OP1(int x, int y)
{
int now = x;
auto it = s1.upper_bound({x, inf});
--it;
int nowh = (*it).se;
if(y <= nowh) return;
it = s1.lower_bound({x, 0});
int rn = -1;
while(1)
{
if(it == s1.end())
{
rn = n;
break;
}
rn = (*it).fi - 1;
int tx = (*it).fi, ty = (*it).se;
if(ty > y) break;
++it;
s1.erase({tx, ty});
s2.erase({ty, tx});
}
s1.insert({x, y});
s2.insert({y, x});
tr1.update(1, x, rn, pos[y]);
}
void OP2(int x, int y)
{
int res = inf;
auto it = s2.upper_bound({y, 0});
int rn = 1;
if(it != s2.begin())
{
rn = min(x + 1, (*it).se);
if(1 <= rn - 1) res = min(res, pos[y] - pos[rn - 1]);
}
if(rn <= x) res = min(res, tr1.query(1, rn, x));
if(res >= inf) cout << "-1\n";
else cout << res << "\n";
}
void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> pos[i];
pos[n + 1] = 2 * inf;
s1.clear(); s2.clear();
s1.insert({0, 0});
s2.insert({0, 0});
for(int i = 1; i <= n; i++)
{
s1.insert({i, i});
s2.insert({i, i});
}
tr1.build(1, 1, n);
while(m--)
{
int op, x, y;
cin >> op >> x >> y;
if(op == 1) OP1(x, y + 1);
else OP2(x, y);
}
}
int main()
{
// freopen("train.in", "r", stdin);
// freopen("train.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--) solve();
return 0;
}