「LOJ#3146」「APIO2019」路燈
Description
一條 \(n\) 條邊,\(n+1\) 個點的鏈,邊有黑有白。若結點 \(a\) 可以到達 \(b\),需要滿足 \(a\to b\) 的路徑上的邊不能有黑的。現給出 \(0\) 時刻邊的初始狀態,然後隨後 \(1\sim q\) 時刻每時刻有一個事件或查詢:
- \(\texttt{toggle} \ i\):翻轉第 \(i\) 條邊的顏色(黑 \(\Leftrightarrow\) 白)
- \(\texttt{query}\ a\ b\):查詢從 \(0\) 開始到當前時刻,有多少時刻滿足 \(a\) 可以到達 \(b\)。
對於每一個 \(\texttt{query}\)
Hint
\(1\le n, q\le 3\times 10^5\)
Solution
設一個位置 \(x\) 可以到達的最左側位置為 \(L(x)\),右側為 \(R(x)\)。
這裡有一個 用 set
維護連續段 的技巧——對於邊狀態 \(\texttt{0100011}\),可以分段存為:\([1, 1], [2, 2], [3,5], [6, 7]\) 四段。對於一個位置 \(x\),所屬段為 \([l, r]\),那麼有 \(L(x)=l, R(x)= r\)。(好像可以直接線段樹)
當修改時,若將邊 \(x\to x+1\) 變為白色,那麼段 \([L(x), x],[x+1, R(x+1)]\)
設當前時間為 \(t\),總時間為 \(T\)。
考慮如何將一個點對轉化成一個二維點,而這又是本題的關鍵。這樣一來,上面的修改變成了 **矩形加 ** 操作——左下點為 \((L(x), x+1)\),右上為 \((x, R(x))\) 的矩形區域的每個位置加上 \(T-t\),表明 暫定後面時刻都是連通的。
同理,若是白變黑,那麼就是矩形減 \(T-t\)。表明目前看來,後面都不連通。
那麼對於查詢,只要獲取位置 \((a, b)\) 的值即可。若當前兩點是聯通的,由於上文是暫定,於是還要把後面減掉,答案 \(-(T-t)\)。
如何高效執行上述兩個操作?顯然可以 CDQ 但我老寫炸。不如樹套樹吧。把矩形修改差分成四個,單點詢問轉化成區域查詢即可。
時空複雜度 \(O(n\log^2 n)\)。
Code
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : LOJ#3146 APIO2019 路燈
*/
#include <cstdio>
#include <set>
using namespace std;
const int MaxN = 3e5 + 5;
int n, N, T;
bool dat[MaxN];
char str[MaxN];
namespace bit_seg {
const int S = MaxN << 9;
int lc[S], rc[S], total = 0, sum[S];
#define mid ((l + r) >> 1)
int root[MaxN];
#define lbt(x) (x & (-x))
void ins(int& x, int p, int v, int l, int r) {
if (!x) x = ++total;
sum[x] += v;
if (l == r) return;
if (p <= mid) ins(lc[x], p, v, l, mid);
else ins(rc[x], p, v, mid + 1, r);
}
void upd(int r, int c, int val) {
for (; r <= n; r += lbt(r)) ins(root[r], c, val, 1, n + 1);
}
void rectAdd(int xl, int yl, int xr, int yr, int val) {
upd(xl, yl, val);
upd(xr + 1, yr + 1, val);
upd(xl, yr + 1, -val);
upd(xr + 1, yl, -val);
}
int get(int x, int ql, int qr, int l, int r) {
if (!x) return 0;
if (ql <= l && r <= qr) return sum[x];
int ret = 0;
if (l <= mid) ret += get(lc[x], ql, qr, l, mid);
if (r > mid) ret += get(rc[x], ql, qr, mid + 1, r);
return ret;
}
int Query(int r, int c) {
int ret = 0;
for (; r; r -= lbt(r)) ret += get(root[r], 1, c, 1, n + 1);
return ret;
}
};
struct interval {
int l, r;
inline interval(int L, int R) : l(L), r(R) { }
inline bool operator < (const interval& x) const { return r < x.r; }
};
set<interval> itv;
typedef set<interval>::iterator Iter;
#include <algorithm>
signed main() {
scanf("%d%d", &n, &T), N = n++;
scanf("%s", str + 1);
for (int i = 1; i <= n; i++)
itv.insert(interval(i, i));
for (int i = 1; i <= N; i++) {
dat[i] = (str[i] == '1');
if (dat[i]) {
Iter it = --itv.lower_bound(interval(0, i + 1));
int L = it->l;
itv.erase(it), itv.erase(interval(i + 1, i + 1));
itv.insert(interval(L, i + 1));
}
}
for (Iter it = itv.begin(); it != itv.end(); it++)
bit_seg::rectAdd(it->l, it->l, it->r, it->r, T);
for (int t = 1; t <= T; t++) {
char opt[10];
int i, a, b;
scanf("%s", opt);
if (opt[0] == 't') {
scanf("%d", &i);
if (dat[i]) {
Iter it = itv.lower_bound(interval(0, i));
int l1 = it->l, r1 = i, l2 = i + 1, r2 = it->r;
bit_seg::rectAdd(l1, l2, r1, r2, -(T - t));
itv.erase(interval(l1, r2));
itv.insert(interval(l1, r1));
itv.insert(interval(l2, r2));
} else {
Iter it = itv.lower_bound(interval(0, i));
int l1 = it->l, r1 = i, l2 = i + 1, r2 = (++it)->r;
bit_seg::rectAdd(l1, l2, r1, r2, T - t);
itv.erase(interval(l1, r1));
itv.erase(interval(l2, r2));
itv.insert(interval(l1, r2));
}
dat[i] ^= 1;
} else {
scanf("%d%d", &a, &b);
int ans = bit_seg::Query(a, b);
if (itv.lower_bound(interval(0, a)) == itv.lower_bound(interval(0, b)))
printf("%d\n", ans - (T - t));
else printf("%d\n", ans);
}
}
}