注:以下的有些解法不一定是標解,甚至可能麻煩了好幾倍。
LOJ3271. 「JOISC 2020 Day1」建築裝飾 4
題意
給定長度為 $2n$ 的序列 $A_{1..2n}$ 和 $B_{1..2n}$。對於每個 $i\in[1,2n]$,從 $A_i,B_i$ 中選出一個數作為 $C_i$。要求 $C_i$ 不下降,且選 $A$ 和選 $B$ 的位置各有 $n$ 個。輸出任意一種方案。
$1\leq n\leq 5\times 10^5$
題解
設 $dp(i, 0 / 1, j)$ 表示前 $i$ 個數字,當前選了 $A / B$,總共有 $j$ 個選了 $A$,是否可能。可以發現對於同一個 $i$,合法的 $j$ 是一段區間,所以 dp 時只需要記錄這個區間即可。時間復雜度 $O(n)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1000005; 4 struct Intv { 5 int l, r; 6 inline Intv() { l = r = -1; } 7 inline Intv(int fuckl, int fuckr) { l = fuckl; r = fuckr; } 8 inline Intv operator + (const Intv &oth) const { 9 if (l == -1) return oth; 10 if (oth.l == -1) return *this; 11 return Intv(min(l, oth.l), max(r, oth.r)); 12 } 13 inline bool Inside(int x) const { 14 return l <= x && x <= r; 15 } 16 } f[N][2]; 17 int n, A[N], B[N]; 18 char S[N]; 19 inline void fucker(int i, int j) { 20 S[2 * n + 1] = 0; 21 int cc = n; 22 while (i > 1) { 23 if (j == 0) { 24 S[i] = 'A'; 25 cc--; 26 if (A[i - 1] <= A[i] && f[i - 1][0].Inside(cc)) { 27 j = 0; 28 } else { 29 j = 1; 30 } 31 } else { 32 S[i] = 'B'; 33 if (A[i - 1] <= B[i] && f[i - 1][0].Inside(cc)) { 34 j = 0; 35 } else { 36 j = 1; 37 } 38 } 39 i--; 40 } 41 S[1] = j + 'A'; 42 puts(S + 1); 43 } 44 int main() { 45 scanf("%d", &n); 46 for (int i = 1; i <= n * 2; i++) scanf("%d", &A[i]); 47 for (int i = 1; i <= n * 2; i++) scanf("%d", &B[i]); 48 f[1][0] = Intv(1, 1); 49 f[1][1] = Intv(0, 0); 50 for (int i = 2; i <= n * 2; i++) { 51 if (A[i - 1] <= A[i]) f[i][0] = f[i][0] + f[i - 1][0]; 52 if (B[i - 1] <= A[i]) f[i][0] = f[i][0] + f[i - 1][1]; 53 if (f[i][0].l != -1) f[i][0].l++, f[i][0].r++; 54 if (A[i - 1] <= B[i]) f[i][1] = f[i][1] + f[i - 1][0]; 55 if (B[i - 1] <= B[i]) f[i][1] = f[i][1] + f[i - 1][1]; 56 } 57 if (f[n * 2][0].Inside(n)) { 58 fucker(n * 2, 0); 59 } else if (f[n * 2][1].Inside(n)) { 60 fucker(n * 2, 1); 61 } else puts("-1"); 62 return 0; 63 }
LOJ3272. 「JOISC 2020 Day1」漢堡肉
題意
在一個 $10^9\times 10^9$ 的平面上有 $n$ 個矩形,坐標都已經給定。給定一個整數 $K$,你要在平面上選 $K$ 個點(可以重復),使得每個矩形都至少與一個選的點有交。輸出任意一種方案。數據保證有解。
$1\leq n\leq 2\times 10^5, 1\leq K\leq 4$
題解
設所有長方形的右邊界最小值為 $min_{xr}$,左邊界最大值為 $max_{xl}$,同理定義 $min_{yr}, max_{yl}$。
可以證明如果有解那么一定可以使得:存在一個點的橫坐標為 $min_{xr}$,證明可以考慮調整法。
同理證明另外三個值。
上面的結論告訴我們:一定存在解,使得這四個值都存在於解的某個點中。
那么當 $K\leq 3$ 時,根據抽屜原理,一定有一個點同時占領了這四個值中的兩個,直接暴力枚舉四種情況,遞歸進行搜索即可。這一部分時間復雜度 $O(n\times 4^K)$。
(注:當 $max_{xl}\leq min_{xr}$ 或 $max_{yl}\leq min_{yr}$ 時,會簡化為一維的情況。此時並不一定這四個值都出現,但這個搜索依然可以得到解。)
當 $K=4$ 時,除了上面的這個搜索,還有一種情況。就是四個點分別在這四個值框出的矩形的四條邊上。
如果一個矩形與這四條邊中的至少三條有公共點,那么可以忽略這個矩形——因為它一定完整包含了一條邊。
剩下的矩形最多只會與兩條邊有公共點,考慮 2SAT 建圖。對於兩個矩形,如果它們在同一條邊上所需的區間沒有交點,那么它們一定不能同時選擇這條邊,這就是限制條件。
可以排序后加入前綴 / 后綴輔助點優化 2SAT 建圖。由於題目保證有解,所以這個 2SAT 也肯定有解。得到 2SAT 的解后把每條邊的區間求個交就是最終的答案了。
這一部分的時間復雜度瓶頸在於排序,為 $O(n\log n)$。總的時間復雜度為 $O(n(4^K+\log n))$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 //Fast IO start 4 namespace io { 5 const int BUFSIZE = 1 << 20; 6 char ibuf[BUFSIZE], *is = ibuf, *it = ibuf; 7 char obuf[BUFSIZE], *os = obuf, *ot = obuf + BUFSIZE - 1; 8 inline void read_char(char &c) { 9 if (is == it) { 10 it = (is = ibuf) + fread(ibuf, 1, BUFSIZE, stdin); 11 if (is == it) *it++ = EOF; 12 } 13 c = *is++; 14 } 15 template <class T> 16 inline void read_int(T &x) { 17 T f = 1; 18 char c; 19 read_char(c); 20 while (!isdigit(c)) { 21 if (c == '-') { 22 f = -1; 23 } 24 read_char(c); 25 } 26 x = 0; 27 while (isdigit(c)) { 28 x = x * 10 + c - '0'; 29 read_char(c); 30 } 31 x *= f; 32 } 33 inline void flush() { 34 fwrite(obuf, 1, os - obuf, stdout); 35 os = obuf; 36 } 37 inline void print_char(char c) { 38 *os++ = c; 39 if (os == ot) { 40 flush(); 41 } 42 } 43 template <class T> 44 inline void print_int(T x) { 45 static char q[40]; 46 if (!x) { 47 print_char('0'); 48 } else { 49 if (x < 0) { 50 print_char('-'); 51 x = -x; 52 } 53 int top = 0; 54 while (x) { 55 q[top++] = x % 10 + '0'; 56 x /= 10; 57 } 58 while (top--) { 59 print_char(q[top]); 60 } 61 } 62 } 63 struct flusher_t { 64 inline ~flusher_t() { 65 flush(); 66 } 67 } flusher; 68 } 69 using io::read_char; 70 using io::read_int; 71 using io::print_char; 72 using io::print_int; 73 //Fast IO end 74 const int N = 200005; 75 struct Node { 76 int xl, yl, xr, yr; 77 }; 78 int n, K, vis[N]; 79 Node a[N]; 80 pair<int, int> ans[5]; 81 void go(int dep) { 82 int mn_xr = 1e9, mx_xl = 1, mn_yr = 1e9, mx_yl = 1; 83 for (int i = 1; i <= n; i++) if (!vis[i]) { 84 mx_xl = max(mx_xl, a[i].xl); 85 mn_xr = min(mn_xr, a[i].xr); 86 mx_yl = max(mx_yl, a[i].yl); 87 mn_yr = min(mn_yr, a[i].yr); 88 } 89 if (dep == 1) { 90 if (mx_xl <= mn_xr && mx_yl <= mn_yr) { 91 ans[1] = make_pair(mx_xl, mx_yl); 92 for (int i = 1; i <= K; i++) printf("%d %d\n", ans[i].first, ans[i].second); 93 exit(0); 94 } 95 return; 96 } 97 for (int tx = 0; tx < 2; tx++) { 98 int x = (tx ? mx_xl : mn_xr); 99 for (int ty = 0; ty < 2; ty++) { 100 int y = (ty ? mx_yl : mn_yr); 101 ans[dep] = make_pair(x, y); 102 for (int i = 1; i <= n; i++) if (a[i].xl <= x && x <= a[i].xr && a[i].yl <= y && y <= a[i].yr) 103 vis[i]++; 104 go(dep - 1); 105 for (int i = 1; i <= n; i++) if (a[i].xl <= x && x <= a[i].xr && a[i].yl <= y && y <= a[i].yr) 106 vis[i]--; 107 } 108 } 109 } 110 const int V = N * 6; 111 vector<int> G[V], h[N]; 112 inline void add_edge(int u, int v) { G[u].push_back(v); } 113 int tot, id[N][2], pre[4][N], suf[4][N], dfn[V], low[V], dfc, stk[V], tp, instk[V], bel[V], scc; 114 vector<tuple<int, int, int> > sl[4], sr[4]; 115 void Tarjan(int u) { 116 dfn[u] = low[u] = ++dfc; 117 stk[++tp] = u; 118 instk[u] = 1; 119 for (int v : G[u]) { 120 if (!dfn[v]) { 121 Tarjan(v); 122 low[u] = min(low[u], low[v]); 123 } else if (instk[v]) { 124 low[u] = min(low[u], dfn[v]); 125 } 126 } 127 if (dfn[u] == low[u]) { 128 ++scc; 129 int w; 130 do { 131 w = stk[tp--]; 132 instk[w] = 0; 133 bel[w] = scc; 134 } while (w != u); 135 } 136 } 137 void solve4() { 138 int mn_xr = 1e9, mx_xl = 1, mn_yr = 1e9, mx_yl = 1; 139 for (int i = 1; i <= n; i++) { 140 mx_xl = max(mx_xl, a[i].xl); 141 mn_xr = min(mn_xr, a[i].xr); 142 mx_yl = max(mx_yl, a[i].yl); 143 mn_yr = min(mn_yr, a[i].yr); 144 } 145 assert(mn_xr <= mx_xl && mn_yr <= mx_yl); 146 tot = 0; 147 for (int i = 1; i <= n; i++) { 148 vector<int> vs; 149 if (a[i].xl <= mn_xr && mn_xr <= a[i].xr) vs.push_back(0); 150 if (a[i].xl <= mx_xl && mx_xl <= a[i].xr) vs.push_back(1); 151 if (a[i].yl <= mn_yr && mn_yr <= a[i].yr) vs.push_back(2); 152 if (a[i].yl <= mx_yl && mx_yl <= a[i].yr) vs.push_back(3); 153 assert(vs.size()); 154 if ((int)vs.size() >= 3) continue; 155 id[i][0] = ++tot; 156 id[i][1] = ++tot; 157 if ((int)vs.size() == 1) { 158 add_edge(id[i][1], id[i][0]); 159 } 160 for (int j = 0; j < (int)vs.size(); j++) { 161 sl[vs[j]].emplace_back(vs[j] < 2 ? max(mn_yr, a[i].yl) : max(mn_xr, a[i].xl), id[i][j], id[i][j ^ 1]); 162 sr[vs[j]].emplace_back(vs[j] < 2 ? min(mx_yl, a[i].yr) : min(mx_xl, a[i].xr), id[i][j], id[i][j ^ 1]); 163 } 164 h[i] = vs; 165 } 166 for (int v = 0; v < 4; v++) { 167 sort(sl[v].begin(), sl[v].end()); 168 sort(sr[v].begin(), sr[v].end()); 169 for (int i = 0; i < (int)sr[v].size(); i++) { 170 pre[v][i + 1] = ++tot; 171 if (i) add_edge(pre[v][i + 1], pre[v][i]); 172 add_edge(pre[v][i + 1], get<2>(sr[v][i])); 173 } 174 for (int i = 0, j = 0; i < (int)sl[v].size(); i++) { 175 while (j < (int)sr[v].size() && get<0>(sr[v][j]) < get<0>(sl[v][i])) j++; 176 if (j) add_edge(get<1>(sl[v][i]), pre[v][j]); 177 } 178 for (int i = (int)sl[v].size() - 1; ~i; i--) { 179 suf[v][i] = ++tot; 180 if (i < (int)sl[v].size() - 1) add_edge(suf[v][i], suf[v][i + 1]); 181 add_edge(suf[v][i], get<2>(sl[v][i])); 182 } 183 for (int i = (int)sr[v].size() - 1, j = (int)sl[v].size(); ~i; i--) { 184 while (j && get<0>(sl[v][j - 1]) > get<0>(sr[v][i])) j--; 185 if (j < (int)sl[v].size()) add_edge(get<1>(sr[v][i]), suf[v][j]); 186 } 187 } 188 dfc = tp = 0; 189 for (int i = 1; i <= tot; i++) { 190 if (!dfn[i]) Tarjan(i); 191 } 192 int L[5], R[5]; 193 L[0] = L[1] = mn_yr; 194 R[0] = R[1] = mx_yl; 195 L[2] = L[3] = mn_xr; 196 R[2] = R[3] = mx_xl; 197 for (int i = 1; i <= n; i++) { 198 if (!id[i][0]) continue; 199 assert(bel[id[i][0]] != bel[id[i][1]]); 200 int j = h[i][(bel[id[i][0]] < bel[id[i][1]]) ? 0 : 1]; 201 L[j] = max(L[j], j < 2 ? a[i].yl : a[i].xl); 202 R[j] = min(R[j], j < 2 ? a[i].yr : a[i].xr); 203 } 204 assert(L[0] <= R[0] && L[1] <= R[1] && L[2] <= R[2] && L[3] <= R[3]); 205 printf("%d %d\n", mn_xr, L[0]); 206 printf("%d %d\n", mx_xl, L[1]); 207 printf("%d %d\n", L[2], mn_yr); 208 printf("%d %d\n", L[3], mx_yl); 209 exit(0); 210 } 211 int main() { 212 read_int(n); 213 read_int(K); 214 for (int i = 1; i <= n; i++) { 215 read_int(a[i].xl); 216 read_int(a[i].yl); 217 read_int(a[i].xr); 218 read_int(a[i].yr); 219 } 220 go(K); 221 if (K == 4) { 222 solve4(); 223 } 224 assert(false); 225 return 0; 226 }
LOJ3273. 「JOISC 2020 Day1」掃除
題意
有一個邊長為 $n$ 的等腰直角三角形房間,頂點為 $(0,0), (0,n), (n,0)$。初始時,房間內有 $m$ 堆灰塵,坐標都已經給定,同一點處可能有多堆灰塵。我們可以用掃帚進行清掃,掃帚可以看作是一條線段。我們只能用如下方式使用掃帚:
- 把掃帚放在房間內,使得掃帚的一個端點為原點,並且掃帚平行於 $y$ 軸。然后,沿着 $x$ 軸正方向水平移動掃帚,直到不能移動為止(即頂到房間的邊了)。在移動過程中,保持掃帚與 $y$ 軸平行,並且一個端點始終在 $x$ 軸上。如果掃帚長度為 $l$,則在 $(x,y)$ 的灰塵($x<n-l, y\leq l$)將會移動到 $(n-l, y)$。這個過程稱為過程 H。
- 把掃帚放在房間內,使得掃帚的一個端點為原點,並且掃帚平行於 $x$ 軸。然后,沿着 $y$ 軸正方向水平移動掃帚,直到不能移動為止(即頂到房間的邊了)。在移動過程中,保持掃帚與 $x$ 軸平行,並且一個端點始終在 $y$ 軸上。如果掃帚長度為 $l$,則在 $(x,y)$ 的灰塵($x\leq l, y<n-l$)將會移動到 $(x, n-l)$。這個過程稱為過程 V。
房間內將會發生 $q$ 次事件,每次事件 $j$ 都是以下四種中的一個:
- 詢問第 $P_j$ 堆灰塵的坐標
- 使用長度為 $L_j$ 的掃帚,進行了過程 H
- 使用長度為 $L_j$ 的掃帚,進行了過程 V
- 一堆新的灰塵出現在了 $(x_j, y_j)$ 位置。編號就是下一個沒用過的數。
對於每次詢問操作,正確求出灰塵的坐標。
$1\leq n\leq 10^9, 1\leq m\leq 5\times 10^5, 1\leq q\leq 10^6$
題解
把初始的那 $m$ 堆灰塵看作插入操作,與之后的 $q$ 次事件放在一起處理。
我們考慮對操作序列分治。假設現在在處理操作區間 $[l,r]$,中間點為 $mid$。那么我們考慮在這一層處理在 $[l,mid]$ 內插入的點在 $[mid+1,r]$ 內的詢問。在 $[l,r]$ 處理結束時,要保證 $[l,r]$ 內插入的點都處在 $r$ 事件結束時的位置。顯然,我們要先遞歸進入下一層,再處理本層的那些詢問。因為這樣,在處理本層詢問的時候,$[l,mid]$ 內插入的點已經處於 $mid$ 結束時的位置了,可以一起往后執行 $[mid+1,r]$ 的事件。
因為我們分治了,所以處理事件時,點集是固定的,不會插入點了。我們考慮把等腰直角三角形內,灰塵的邊界和內部分開來維護,因為一旦點從內部變成了邊界,那么就一直是邊界了。下面是一張便於理解的圖:
其中紅色的就是邊界,綠色的是掃帚。可以發現,邊界上的 $x,y$ 值都是單調的。在使用掃帚時,我們會把邊界上的一個區間的點的 $x$ 值或 $y$ 值與 $n-l$ 取 $\max$,也會把一個矩形內的點全部移到邊界上。
那么邊界上的點,由於要插入,所以用平衡樹(例如非旋轉 Treap)來維護。
而內部的點,如果沒變成邊界,位置就不會變,如果變成了邊界,就會從內部刪掉,並加入平衡樹。所以用線段樹維護即可。
顯然,每次平衡樹操作的復雜度都是 $O(\log (m+q))$ 的。而線段樹的操作要均攤分析,由於每個點在每一層最多會被刪除一次,所以每個點最多會被刪除 $O(\log (m+q))$ 次,一次刪除的復雜度是 $O(\log (m+q))$。
最終分析下來,時間復雜度是 $O((m+q)\log^2(m+q))$。
實現細節(如果要用非旋轉 Treap 可以看一看):由於這個邊界和操作的特殊性,我們在維護平衡樹時不一定要記錄子樹大小,記錄當前點的坐標即可。以及,為了回答邊界上的點的坐標的詢問,我們需要記錄樹上每個點的父節點,在 Split 和 Merge 時要及時更新父節點。
本題另有對等腰直角三角形分治的做法,具體可以見 zyy 神仙的題解。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 //Fast IO start 4 namespace io { 5 const int BUFSIZE = 1 << 20; 6 char ibuf[BUFSIZE], *is = ibuf, *it = ibuf; 7 char obuf[BUFSIZE], *os = obuf, *ot = obuf + BUFSIZE - 1; 8 inline void read_char(char &c) { 9 if (is == it) { 10 it = (is = ibuf) + fread(ibuf, 1, BUFSIZE, stdin); 11 if (is == it) *it++ = EOF; 12 } 13 c = *is++; 14 } 15 template <class T> 16 inline void read_int(T &x) { 17 T f = 1; 18 char c; 19 read_char(c); 20 while (!isdigit(c)) { 21 if (c == '-') { 22 f = -1; 23 } 24 read_char(c); 25 } 26 x = 0; 27 while (isdigit(c)) { 28 x = x * 10 + c - '0'; 29 read_char(c); 30 } 31 x *= f; 32 } 33 inline void flush() { 34 fwrite(obuf, 1, os - obuf, stdout); 35 os = obuf; 36 } 37 inline void print_char(char c) { 38 *os++ = c; 39 if (os == ot) { 40 flush(); 41 } 42 } 43 template <class T> 44 inline void print_int(T x) { 45 static char q[40]; 46 if (!x) { 47 print_char('0'); 48 } else { 49 if (x < 0) { 50 print_char('-'); 51 x = -x; 52 } 53 int top = 0; 54 while (x) { 55 q[top++] = x % 10 + '0'; 56 x /= 10; 57 } 58 while (top--) { 59 print_char(q[top]); 60 } 61 } 62 } 63 struct flusher_t { 64 inline ~flusher_t() { 65 flush(); 66 } 67 } flusher; 68 } 69 using io::read_char; 70 using io::read_int; 71 using io::print_char; 72 using io::print_int; 73 //Fast IO end 74 const int N = 1500005; 75 mt19937 gen(1145141); 76 int n, m, clk; 77 pair<int, int> pts[N], ans[N]; 78 int qt[N], qc[N], tag[N], del[N]; 79 pair<pair<int, int>, int> tp[N]; 80 namespace SegTree { 81 pair<int, int> tr[N * 4]; 82 void build(int i, int l, int r) { 83 if (l == r) { 84 tr[i] = make_pair(tp[l].second, l); 85 return; 86 } 87 int mid = (l + r) >> 1; 88 build(i << 1, l, mid); 89 build(i << 1 | 1, mid + 1, r); 90 tr[i] = (pts[tr[i << 1].first].second < pts[tr[i << 1 | 1].first].second ? tr[i << 1] : tr[i << 1 | 1]); 91 } 92 pair<int, int> query(int i, int l, int r, int lf, int rg) { 93 if (lf <= l && r <= rg) return tr[i]; 94 int mid = (l + r) >> 1; 95 if (rg <= mid) return query(i << 1, l, mid, lf, rg); 96 else if (lf > mid) return query(i << 1 | 1, mid + 1, r, lf, rg); 97 pair<int, int> r1 = query(i << 1, l, mid, lf, rg), r2 = query(i << 1 | 1, mid + 1, r, lf, rg); 98 return ((pts[r1.first].second < pts[r2.first].second) ? r1 : r2); 99 } 100 void update(int i, int l, int r, int pos) { 101 if (l == r) { 102 tr[i].first = 0; 103 return; 104 } 105 int mid = (l + r) >> 1; 106 if (pos <= mid) update(i << 1, l, mid, pos); 107 else update(i << 1 | 1, mid + 1, r, pos); 108 tr[i] = (pts[tr[i << 1].first].second < pts[tr[i << 1 | 1].first].second ? tr[i << 1] : tr[i << 1 | 1]); 109 } 110 } 111 namespace Treap { 112 struct Node { 113 int lson, rson, par, rnd, x, y, tagx, tagy; 114 inline void upd(int tx, int ty) { 115 x = max(x, tx); 116 tagx = max(tagx, tx); 117 y = max(y, ty); 118 tagy = max(tagy, ty); 119 } 120 inline void pushdown(); 121 } tr[N]; 122 int tot, root; 123 inline void Init() { 124 tot = root = 0; 125 tr[0].lson = tr[0].rson = tr[0].par = tr[0].rnd = tr[0].x = tr[0].y = tr[0].tagx = tr[0].tagy = 0; 126 } 127 inline int NewNode(int x, int y) { 128 int p = ++tot; 129 tr[p].lson = tr[p].rson = tr[p].par = tr[p].tagx = tr[p].tagy = 0; 130 tr[p].rnd = gen(); 131 tr[p].x = x, tr[p].y = y; 132 return p; 133 } 134 inline void Node::pushdown() { 135 if (tagx || tagy) { 136 if (lson) tr[lson].upd(tagx, tagy); 137 if (rson) tr[rson].upd(tagx, tagy); 138 tagx = tagy = 0; 139 } 140 } 141 inline pair<int, int> Split_y(int p, int y) { 142 if (!p) return make_pair(0, 0); 143 tr[p].pushdown(); 144 if (tr[p].y <= y) { 145 pair<int, int> q = Split_y(tr[p].lson, y); 146 tr[p].lson = q.second; 147 if (tr[p].lson) tr[tr[p].lson].par = p; 148 q.second = p; 149 tr[p].par = 0; 150 return q; 151 } else { 152 pair<int, int> q = Split_y(tr[p].rson, y); 153 tr[p].rson = q.first; 154 if (tr[p].rson) tr[tr[p].rson].par = p; 155 q.first = p; 156 tr[p].par = 0; 157 return q; 158 } 159 } 160 inline pair<int, int> Split_x(int p, int x) { 161 if (!p) return make_pair(0, 0); 162 tr[p].pushdown(); 163 if (tr[p].x > x) { 164 pair<int, int> q = Split_x(tr[p].lson, x); 165 tr[p].lson = q.second; 166 if (tr[p].lson) tr[tr[p].lson].par = p; 167 q.second = p; 168 tr[p].par = 0; 169 return q; 170 } else { 171 pair<int, int> q = Split_x(tr[p].rson, x); 172 tr[p].rson = q.first; 173 if (tr[p].rson) tr[tr[p].rson].par = p; 174 q.first = p; 175 tr[p].par = 0; 176 return q; 177 } 178 } 179 int Merge(int p1, int p2) { 180 if (!p1 || !p2) return p1 | p2; 181 if (tr[p1].rnd < tr[p2].rnd) { 182 tr[p1].pushdown(); 183 tr[p1].rson = Merge(tr[p1].rson, p2); 184 if (tr[p1].rson) tr[tr[p1].rson].par = p1; 185 return p1; 186 } else { 187 tr[p2].pushdown(); 188 tr[p2].lson = Merge(p1, tr[p2].lson); 189 if (tr[p2].lson) tr[tr[p2].lson].par = p2; 190 return p2; 191 } 192 } 193 int Insert(int x, int y) { 194 int p = NewNode(x, y); 195 int a, b, c; 196 tie(b, c) = Split_x(root, x); 197 tie(a, b) = Split_y(b, y - 1); 198 root = Merge(Merge(a, p), Merge(b, c)); 199 return p; 200 } 201 inline pair<int, int> query(int p) { 202 int x = tr[p].x, y = tr[p].y; 203 while (p) { 204 x = max(x, tr[p].tagx); 205 y = max(y, tr[p].tagy); 206 p = tr[p].par; 207 } 208 return make_pair(x, y); 209 } 210 } 211 int nid[N]; 212 void solve(int l, int r) { 213 if (l == r) return; 214 int mid = (l + r) >> 1; 215 solve(l, mid); 216 solve(mid + 1, r); 217 int tot = 0; 218 clk++; 219 for (int i = l; i <= mid; i++) { 220 if (qt[i] == 4) { 221 tp[++tot] = make_pair(pts[qc[i]], qc[i]); 222 tag[qc[i]] = clk; 223 del[qc[i]] = 0; 224 } 225 } 226 if (!tot) return; 227 sort(tp + 1, tp + 1 + tot); 228 SegTree::build(1, 1, tot); 229 Treap::Init(); 230 for (int i = mid + 1; i <= r; i++) { 231 if (qt[i] == 4 || (qt[i] == 1 && tag[qc[i]] != clk)) continue; 232 if (qt[i] == 1) { 233 ans[i] = (del[qc[i]] ? Treap::query(nid[qc[i]]) : pts[qc[i]]); 234 } else if (qt[i] == 2) { 235 int p, q; 236 tie(p, q) = Treap::Split_y(Treap::root, qc[i]); 237 if (q) Treap::tr[q].upd(n - qc[i], 0); 238 Treap::root = Treap::Merge(p, q); 239 int xr = lower_bound(tp + 1, tp + 1 + tot, make_pair(make_pair(n - qc[i], n + 5), 0)) - tp - 1; 240 if (!xr) continue; 241 pair<int, int> pr; 242 while (true) { 243 pr = SegTree::query(1, 1, tot, 1, xr); 244 if (pts[pr.first].second > qc[i]) break; 245 nid[pr.first] = Treap::Insert(n - qc[i], pts[pr.first].second); 246 del[pr.first] = 1; 247 SegTree::update(1, 1, tot, pr.second); 248 } 249 } else if (qt[i] == 3) { 250 int p, q; 251 tie(p, q) = Treap::Split_x(Treap::root, qc[i]); 252 if (p) Treap::tr[p].upd(0, n - qc[i]); 253 Treap::root = Treap::Merge(p, q); 254 int xr = lower_bound(tp + 1, tp + 1 + tot, make_pair(make_pair(qc[i], n + 5), 0)) - tp - 1; 255 if (!xr) continue; 256 pair<int, int> pr; 257 while (true) { 258 pr = SegTree::query(1, 1, tot, 1, xr); 259 if (pts[pr.first].second > n - qc[i]) break; 260 nid[pr.first] = Treap::Insert(pts[pr.first].first, n - qc[i]); 261 del[pr.first] = 1; 262 SegTree::update(1, 1, tot, pr.second); 263 } 264 } 265 } 266 for (int i = l; i <= mid; i++) { 267 if (qt[i] == 4 && del[qc[i]]) { 268 pts[qc[i]] = Treap::query(nid[qc[i]]); 269 } 270 } 271 } 272 int main() { 273 int q, cnt; 274 read_int(n); 275 read_int(m); 276 read_int(q); 277 for (int i = 1; i <= m; i++) { 278 read_int(pts[i].first); 279 read_int(pts[i].second); 280 qt[i] = 4; 281 qc[i] = i; 282 } 283 cnt = m; 284 for (int i = m + 1; i <= m + q; i++) { 285 read_int(qt[i]); 286 if (qt[i] <= 3) read_int(qc[i]); 287 else { 288 qc[i] = ++cnt; 289 read_int(pts[cnt].first); 290 read_int(pts[cnt].second); 291 } 292 } 293 m += q; 294 clk = 0; 295 pts[0].second = n + 5; 296 solve(1, m); 297 for (int i = 1; i <= m; i++) { 298 if (qt[i] == 1) printf("%d %d\n", ans[i].first, ans[i].second); 299 } 300 return 0; 301 }
LOJ3274. 「JOISC 2020 Day2」變色龍之戀
題意
這是一道交互題。
有 $2n$ 只變色龍,其中 $n$ 只是性別 X,另外 $n$ 只是性別 Y。
每只變色龍都有一個原色。已知所有性別 X 的變色龍的原色互不相同,且對於每只性別 X 的變色龍,都存在唯一的原色與之相同的性別為 Y 的變色龍。
春天到了,每只變色龍都愛上了一只與之性別不同且與之顏色不同的變色龍,且不存在兩只變色龍愛上同一只變色龍的情況。
你可以召集一些變色龍進行會議。對於一只參加會議的變色龍 $s$,設其喜歡的變色龍為 $t$。$s$ 的膚色由以下方式決定:
如果 $t$ 參加了這場會議,那么 $s$ 的膚色為 $t$ 的原色,否則 $s$ 的膚色為 $s$ 的原色。
一只變色龍的顏色可以在不同的會議間發生改變。
對於你組織的會議,你可以得到場上所有變色龍的膚色的種類數。
請在 $20000$ 次會議內,確定每一對具有相同原色的變色龍。
$2\leq n\leq 500$
題解
如果我們對於每一對點 $(u,v)$ 都詢問一遍,那么回答為 $1$ 只有以下三種可能:
- $u,v$ 同色
- $u$ 喜歡 $v$ 且 $v$ 不喜歡 $u$
- $v$ 喜歡 $u$ 且 $u$ 不喜歡 $v$
那么,我們把所有回答為 $1$ 的 $(u,v)$ 之間都連一條無向邊。顯然,一個點的度數要么為 $1$ 要么為 $3$。如果度數為 $1$ 那么已經確認顏色了。如果度數為 $3$ 的話,設 $a,b$ 同色、$a$ 喜歡 $c$、$d$ 喜歡 $a$,我們考慮問 $\{a\}$ 並 $\{b,c,d\}$ 中的大小為 $2$ 的子集。那么當我們問的是 $\{a,b,d\}$ 時,回答是 $1$,問其他的回答都是 $2$,以此可以確定 $a$ 喜歡的人。全部這樣跑就可以確定相同的顏色了。
這樣的瓶頸在於前面的確定邊,詢問次數是 $O(n^2)$ 的。
優化考慮這張圖是二分圖的性質。我們按順序確定 $1..2n$ 與前面的點連的邊。當進入一個點 $i$ 時,我們把 $1..i-1$ 這些點二染色,弄出二分圖。
注意到如果 $S$ 是獨立集,$u\notin S$,那么 $S\cup \{u\}$ 是獨立集 當且僅當 詢問 $S\cup \{u\}$ 的回答為 $|S|+1$。
因此我們可以考慮二分確定邊,這樣確定一個點的復雜度為 $O(\log n)$。(另外,為了減小詢問時的常數,可以考慮按一個隨機順序詢問)
確定邊后執行暴力做法的后半段即可確定相同的顏色了。
總的詢問復雜度為 $O(n\log n)$。
代碼

1 #include "chameleon.h" 2 #include <bits/stdc++.h> 3 using namespace std; 4 namespace { 5 const int N = 1005; 6 mt19937 gen(1145141); 7 int id[N], col[N], vis[N], love[N], fr[N]; 8 vector<int> G[N], cs[2]; 9 void dfs(int u, int c) { 10 cs[col[u] = c].push_back(u); 11 for (int v : G[u]) if (col[v] == -1) dfs(v, c ^ 1); 12 } 13 bool has_e(int c, int l, int r, int u) { 14 vector<int> vec; 15 vec.push_back(u); 16 for (int i = l; i <= r; i++) if (!vis[cs[c][i]]) vec.push_back(cs[c][i]); 17 return Query(vec) < (int)vec.size(); 18 } 19 int dc(int c, int l, int r, int u) { 20 if (l == r) return cs[c][l]; 21 int mid = (l + r) >> 1; 22 if (has_e(c, l, mid, u)) return dc(c, l, mid, u); 23 else return dc(c, mid + 1, r, u); 24 } 25 } 26 void Solve(int n) { 27 for (int i = 1; i <= 2 * n; i++) id[i] = i, love[i] = fr[i] = 0; 28 shuffle(id + 1, id + 1 + 2 * n, gen); 29 for (int i = 1; i <= 2 * n; i++) { 30 int u = id[i]; 31 for (int j = 1; j < i; j++) col[id[j]] = -1, vis[id[j]] = 0; 32 cs[0].clear(), cs[1].clear(); 33 for (int j = 1; j < i; j++) if (col[id[j]] == -1) dfs(id[j], 0); 34 for (int c = 0; c < 2; c++) { 35 while (has_e(c, 0, (int)cs[c].size() - 1, u)) { 36 int v = dc(c, 0, (int)cs[c].size() - 1, u); 37 G[u].push_back(v); 38 G[v].push_back(u); 39 vis[v] = 1; 40 } 41 } 42 } 43 for (int i = 1; i <= 2 * n; i++) if ((int)G[i].size() == 3) { 44 int id; 45 if (Query({i, G[i][0], G[i][1]}) == 1) id = 2; 46 else if (Query({i, G[i][0], G[i][2]}) == 1) id = 1; 47 else id = 0; 48 love[i] = G[i][id]; 49 fr[G[i][id]] = i; 50 } 51 for (int i = 1; i <= 2 * n; i++) { 52 int id; 53 if (G[i][0] != love[i] && G[i][0] != fr[i]) id = 0; 54 else if (G[i][1] != love[i] && G[i][1] != fr[i]) id = 1; 55 else id = 2; 56 if (i < G[i][id]) Answer(i, G[i][id]); 57 } 58 }
LOJ3275. 「JOISC 2020 Day2」有趣的 Joitter 交友
題意
有一張 $n$ 個點的有向圖,初始沒有邊,現在會有 $m$ 條邊依次加進圖中。每加進一條邊時,系統會自動進行如下步驟,直到不能操作為止:
- 選擇一個點 $x$,選擇一個點 $y$,使得 $x\rightarrow y$ 有邊。選擇一個點 $z$,要求 $z\neq x$,$x\rightarrow z$ 沒有邊,且 $y,z$ 之間有二元環。然后讓 $x\rightarrow z$ 連一條邊。
每加進去一條邊,都要算出這張圖中現在有多少條邊。(當然,重邊只能算一次,且題目保證無自環)
$2\leq n\leq 10^5, 1\leq m\leq 3\times 10^5$
題解
可以發現如果 $u, v$ 之間有二元環,$v, w$ 之間有二元環,那么 $u, w$ 之間也有二元環,即二元環的關系是傳遞的,可以看作“二元環連通塊”。而且對於一條邊 $(u, v)$,$u$ 會向 $v$ 所在的二元環連通塊全部連一條邊。那么維護好所有的二元環連通塊即可很方便地計算答案。合並兩個二元環連通塊時,需要用啟發式合並。
時間復雜度 $O(m\log^2 n)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 //Fast IO start 4 namespace io { 5 const int BUFSIZE = 1 << 20; 6 char ibuf[BUFSIZE], *is = ibuf, *it = ibuf; 7 char obuf[BUFSIZE], *os = obuf, *ot = obuf + BUFSIZE - 1; 8 inline void read_char(char &c) { 9 if (is == it) { 10 it = (is = ibuf) + fread(ibuf, 1, BUFSIZE, stdin); 11 if (is == it) *it++ = EOF; 12 } 13 c = *is++; 14 } 15 template <class T> 16 inline void read_int(T &x) { 17 T f = 1; 18 char c; 19 read_char(c); 20 while (!isdigit(c)) { 21 if (c == '-') { 22 f = -1; 23 } 24 read_char(c); 25 } 26 x = 0; 27 while (isdigit(c)) { 28 x = x * 10 + c - '0'; 29 read_char(c); 30 } 31 x *= f; 32 } 33 inline void flush() { 34 fwrite(obuf, 1, os - obuf, stdout); 35 os = obuf; 36 } 37 inline void print_char(char c) { 38 *os++ = c; 39 if (os == ot) { 40 flush(); 41 } 42 } 43 template <class T> 44 inline void print_int(T x) { 45 static char q[40]; 46 if (!x) { 47 print_char('0'); 48 } else { 49 if (x < 0) { 50 print_char('-'); 51 x = -x; 52 } 53 int top = 0; 54 while (x) { 55 q[top++] = x % 10 + '0'; 56 x /= 10; 57 } 58 while (top--) { 59 print_char(q[top]); 60 } 61 } 62 } 63 struct flusher_t { 64 inline ~flusher_t() { 65 flush(); 66 } 67 } flusher; 68 } 69 using io::read_char; 70 using io::read_int; 71 using io::print_char; 72 using io::print_int; 73 //Fast IO end 74 const int N = 100005; 75 int n, m, dsu[N], sz[N]; 76 long long ans; 77 map<int, set<int> > Gto[N]; 78 set<int> Gfr[N]; 79 int sfr[N]; 80 int Find(int x) { 81 return x == dsu[x] ? x : dsu[x] = Find(dsu[x]); 82 } 83 void Merge(set<int> &st1, set<int> &st2) { 84 if (st1.size() < st2.size()) st1.swap(st2); 85 for (auto &x : st2) st1.insert(x); 86 st2.clear(); 87 } 88 void Union(int x, int y) { 89 x = Find(x); y = Find(y); 90 if (x == y) return; 91 if (sz[x] < sz[y]) swap(x, y); 92 dsu[y] = x; 93 ans -= 1ll * Gto[x][y].size() * sz[y]; 94 ans -= 1ll * Gto[y][x].size() * sz[x]; 95 sfr[x] -= Gto[y][x].size(); 96 sfr[y] -= Gto[x][y].size(); 97 Gto[x].erase(y); 98 Gto[y].erase(x); 99 Gfr[x].erase(y); 100 Gfr[y].erase(x); 101 ans += 2ll * sz[x] * sz[y]; 102 ans -= 1ll * sfr[x] * sz[x]; 103 ans -= 1ll * sfr[y] * sz[y]; 104 sz[x] += sz[y]; 105 set<int> may; 106 for (map<int, set<int> >::iterator it = Gto[y].begin(); it != Gto[y].end(); ++it) { 107 may.insert(it -> first); 108 Merge(Gto[x][it -> first], it -> second); 109 Gfr[it -> first].erase(y); 110 Gfr[it -> first].insert(x); 111 } 112 Gto[y].clear(); 113 for (set<int>::iterator it = Gfr[y].begin(); it != Gfr[y].end(); ++it) { 114 may.insert(*it); 115 Gfr[x].insert(*it); 116 sfr[x] -= Gto[*it][x].size(); 117 Merge(Gto[*it][x], Gto[*it][y]); 118 sfr[x] += Gto[*it][x].size(); 119 Gto[*it].erase(y); 120 } 121 Gfr[y].clear(); 122 ans += 1ll * sfr[x] * sz[x]; 123 for (auto &z : may) { 124 if (Find(x) != Find(z)) { 125 if (Gfr[x].count(z) && Gfr[z].count(x)) Union(x, z); 126 } 127 } 128 } 129 void Gao(int u, int v) { 130 if (Find(u) == Find(v)) return; 131 map<int, set<int> >::iterator fuckit = Gto[Find(u)].find(Find(v)); 132 if (fuckit != Gto[Find(u)].end() && (fuckit -> second).count(u)) return; 133 Gto[Find(u)][Find(v)].insert(u); 134 Gfr[Find(v)].insert(Find(u)); 135 sfr[Find(v)]++; 136 ans += sz[Find(v)]; 137 if (Gto[Find(v)].find(Find(u)) != Gto[Find(v)].end()) Union(u, v); 138 } 139 int main() { 140 read_int(n); 141 read_int(m); 142 for (int i = 1; i <= n; i++) dsu[i] = i, sz[i] = 1; 143 ans = 0; 144 for (int i = 1; i <= m; i++) { 145 int u, v; 146 read_int(u); 147 read_int(v); 148 Gao(u, v); 149 print_int(ans); 150 print_char('\n'); 151 } 152 return 0; 153 }
LOJ3276. 「JOISC 2020 Day2」遺跡
題意
很久很久以前,有 $2n$ 根石柱,編號為 $1..2n$。對於任意 $k\in[1,n]$,恰好有兩根石柱高度為 $k$。
隨后發生了 $n$ 次地震。每次地震會把所有未被保護的石柱的高度減 $1$,而被保護的石柱高度不變。在這 $n$ 次地震后,只剩下 $n$ 根石柱了,即只有 $n$ 根石柱的高度至少為 $1$。
每次地震發生前,古人類按照如下方式保護石柱:對於任意 $k\in[1,n]$,只保護恰好一根高度為 $k$ 的石柱,如有多個則保護編號最大的那個。
現在已知 $n$ 次地震后剩下的石柱的編號,求出初始時 $2n$ 根石柱的高度組合種數模 $10^9+7$。
$1\leq n\leq 600$
題解
(為了方便起見,我們先約定兩個相同高度的數是不同的,最后再把答案除以 $2^n$)
如果給定了初始序列 $a$,如何得到最終剩下的石柱,可以用如下代碼實現:
1 for (int i = 2 * n; i; i--) { 2 int found = 0; 3 for (int j = a[i]; j; j--) { 4 if (!vis[j]) { 5 found = 1; 6 vis[j] = 1; 7 break; 8 } 9 } 10 // found 為 1 則 i 是剩下的石柱,且最終高度為 j,為 0 則 i 是不剩下的石柱,即最終高度為 0 的石柱 11 }
那么我們考慮 $i$ 從大到小掃。可以發現,一個石柱的初始高度 $h$ 會變成 $0$,當且僅當 $1..h$ 已經被 $vis$ 過了。
所以我們只關心到目前為止,最長的全部 $vis$ 過的前綴有多長。那么石柱會剩下當且僅當其初始高度大於這個最長前綴。
設 $f(i, j)$ 表示從 $2n$ 掃到 $i$,當前最長的全部 $vis$ 過的前綴為 $j$,只考慮 不剩下的石柱 和 最終高度 $\leq j$ 的石柱 的貢獻,目前的方案數。初始值就是 $f(2n+1, 0)=1$。
下設 $s0$ 表示 $i+1$ 到 $2n$ 的不剩下的石柱個數,$s1$ 同理表示剩下的石柱的個數。
轉移的話,如果第 $i$ 個石柱最終不剩下,那么它的初始高度肯定是 $\leq j$ 的,則它的初始高度選法有 $j-s0$ 種。(高度 $\leq j$ 的有 $2j$ 個數,去掉剩下的石柱的 $j$ 種,再去掉之前不剩下的石柱的 $s0$ 種,就是 $j-s0$ 了)
如果第 $i$ 個石柱最終剩下,那么有兩種可能:
一種是它並沒有讓 $j$ 增加,根據定義,不增加更多貢獻。
另一種是它讓 $j$ 變大了,我們枚舉它變成了 $k(k>j)$。那么 $f(i+1, j)$ 對 $f(i, k)$ 的貢獻是 $f(i+1, j)\times\binom{s1-j}{k-j-1}\times ways(k-j-1)\times (k-j+1)$。
其中 $ways(i)$ 的意思是:有 $i$ 根石柱,要給它們安排 $1..i$ 中的初始高度,每種高度數量不超過 $2$,且它們最終高度是 $1..i$ 的方案數。我們馬上會給出它的求法。
現在先理解一下這個貢獻:先是 $f(i+1, j)$ 表示之前的貢獻,$\binom{s1-j}{k-j-1}$ 表示之前高度 $>j+1$ 的石柱中,選出一些來作為新的前綴,$ways(k-j-1)$ 表示選出的這些的石柱的初始高度安排的方案數,$(k-j+1)$ 表示第 $i$ 根石柱的初始高度選法。(把之前的石柱和第 $i$ 根石柱分開來搞,是因為第 $i$ 根石柱的最終高度必須是 $j+1$)
再來看一下 $ways(i)$ 的求法。我們要知道結論:設 $ps_j$ 表示初始高度 $\leq j$ 的石柱個數,那么這些石柱的最終高度是 $1..i$ 當且僅當 對於任意 $j\leq i$,有 $ps_j\leq j$(即每個前綴都不能容下更多的數),且 $ps_i=i$(即數的個數必須是 $i$)。
於是我們考慮 $g(i, j)$ 表示考慮了高度 $1..i$,$ps_i$ 為 $j$ 的合法方案數。初始值為 $g(0,0)=1$。轉移只需枚舉下一個數有幾個即可。注意由於最開始我們規定兩個相同高度的數是不同的,因此在某些情況下要讓貢獻乘 $2$,具體見代碼。
則 $ways(i)=g(i,i)$。
最終的答案就是 $f(1, n)$。別忘了除以 $2^n$。
求 $g$ 的時間復雜度為 $O(n^2)$,求 $f$ 的時間復雜度為 $O(n^3)$,總的時間復雜度為 $O(n^3)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 1205; 4 const int mod = 1000000007; 5 int n, has[N], binom[N][N], f[N][N], g[N][N], ways[N]; 6 int main() { 7 scanf("%d", &n); 8 for (int i = binom[0][0] = 1; i <= n; i++) { 9 for (int j = binom[i][0] = 1; j <= i; j++) { 10 binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % mod; 11 } 12 } 13 g[0][0] = 1; 14 for (int i = 1; i <= n; i++) { 15 for (int j = 0; j < i; j++) if (g[i - 1][j]) { 16 for (int k = 0; k <= 2; k++) if (j + k <= i) { 17 g[i][j + k] = (g[i][j + k] + (k ? 2ll : 1ll) * g[i - 1][j] * binom[j + k][k]) % mod; 18 } 19 } 20 } 21 for (int i = 0; i <= n; i++) { 22 ways[i] = g[i][i]; 23 } 24 for (int i = 1; i <= n; i++) { 25 int x; 26 scanf("%d", &x); 27 has[x] = 1; 28 } 29 f[2 * n + 1][0] = 1; 30 int s0 = 0, s1 = 0; 31 for (int i = 2 * n; i; i--) { 32 if (!has[i]) { 33 for (int j = 0; j <= n; j++) if (f[i + 1][j]) { 34 f[i][j] = 1ll * f[i + 1][j] * max(0, j - s0) % mod; 35 } 36 s0++; 37 } else { 38 for (int j = 0; j <= n; j++) f[i][j] = f[i + 1][j]; 39 for (int j = 0; j <= n; j++) if (f[i + 1][j]) { 40 for (int k = j + 1; k <= n; k++) { 41 f[i][k] = (f[i][k] + 1ll * f[i + 1][j] * binom[s1 - j][k - j - 1] % mod * ways[k - j - 1] % mod * (k - j + 1)) % mod; 42 } 43 } 44 s1++; 45 } 46 } 47 int ans = f[1][n]; 48 for (int i = 1; i <= n; i++) ans = ((ans & 1) ? ans + mod : ans) >> 1; 49 printf("%d\n", ans); 50 return 0; 51 }
LOJ3277. 「JOISC 2020 Day3」星座 3
題意
有一張 $n\times n$ 的星空圖,將左起第 $x$ 列,下起第 $y$ 行的像素點稱為 $(x,y)$。畫面里有白色的樓,黃色的星星,黑色的天空。第 $i$ 列從最下方起 $A_i$ 行都是白色的樓。有 $m$ 個星星,位置也已經給定。其他的所有像素點都是黑色的天空。
一個長方形區域能被稱為星座,當且僅當它里面不含白色的點,且至少存在兩個星星。
我們可以把一些星星塗成黑色,使得沒有星座存在。但把每個星星 $j$ 塗成黑色有 $C_j$ 的代價。求最小使用多少代價可以滿足條件。
$1\leq n,m\leq 2\times 10^5$
題解
對於所有不包含白色的長方形,我們只考慮極大的。即建出樓的笛卡爾樹,樹上每個點會對應一個極大的長方形。
然后,可以發現,如果我們選擇一個星星,並把它保留,那么樹上會有一條祖孫鏈都不能保留其他星星了,但其他的地方不會影響。
因此我們考慮 $f_i$ 表示以 $i$ 為子樹的最大保留代價,轉移的時候枚舉這個點內是否保留星星以及保留哪一個,BIT 維護即可。
答案就是所有代價的和 減去 最大保留代價。
時間復雜度 $O(n\log n)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 200005; 4 struct Node { 5 int x, y, w; 6 }; 7 vector<Node> pts[N]; 8 pair<int, int> tr[N * 4]; 9 void build(int i, int l, int r) { 10 if (l == r) { 11 if (pts[l].size()) tr[i] = make_pair(pts[l].back().y, l); 12 else tr[i] = make_pair(-1, l); 13 return; 14 } 15 int mid = (l + r) >> 1; 16 build(i << 1, l, mid); 17 build(i << 1 | 1, mid + 1, r); 18 tr[i] = max(tr[i << 1], tr[i << 1 | 1]); 19 } 20 pair<int, int> Query(int i, int l, int r, int lf, int rg) { 21 if (lf <= l && r <= rg) return tr[i]; 22 int mid = (l + r) >> 1; 23 if (rg <= mid) return Query(i << 1, l, mid, lf, rg); 24 else if (lf > mid) return Query(i << 1 | 1, mid + 1, r, lf, rg); 25 else return max(Query(i << 1, l, mid, lf, rg), Query(i << 1 | 1, mid + 1, r, lf, rg)); 26 } 27 void Modify(int i, int l, int r, int pos) { 28 if (l == r) { 29 if (pts[l].size()) tr[i] = make_pair(pts[l].back().y, l); 30 else tr[i] = make_pair(-1, l); 31 return; 32 } 33 int mid = (l + r) >> 1; 34 if (pos <= mid) Modify(i << 1, l, mid, pos); 35 else Modify(i << 1 | 1, mid + 1, r, pos); 36 tr[i] = max(tr[i << 1], tr[i << 1 | 1]); 37 } 38 int n, m, A[N], lg2[N], ST[18][N]; 39 inline int Qmax(int l, int r) { 40 int i = lg2[r - l + 1]; 41 if (A[ST[i][l]] >= A[ST[i][r - (1 << i) + 1]]) 42 return ST[i][l]; 43 else 44 return ST[i][r - (1 << i) + 1]; 45 } 46 int K, mxi[N], repL[N], repR[N], dR[N], bel[N]; 47 vector<int> G[N]; 48 vector<Node> np[N]; 49 struct BIT { 50 int n; 51 long long bit[N]; 52 inline void Init(int _n) { 53 n = _n; 54 for (int i = 1; i <= n; i++) bit[i] = 0; 55 } 56 inline void add(int i, long long x) { 57 while (i <= n) { 58 bit[i] += x; 59 i += i & -i; 60 } 61 } 62 inline long long sum(int i) { 63 long long res = 0; 64 while (i > 0) { 65 res += bit[i]; 66 i -= i & -i; 67 } 68 return res; 69 } 70 } bit1; 71 long long dp[N]; 72 void dfs2(int u) { 73 dp[u] = 0; 74 for (int v : G[u]) dfs2(v), dp[u] += dp[v]; 75 for (int v : G[u]) { 76 bit1.add(v, dp[u] - dp[v]); 77 bit1.add(dR[v] + 1, dp[v] - dp[u]); 78 } 79 bit1.add(u, dp[u]); 80 bit1.add(u + 1, -dp[u]); 81 for (auto &P : np[u]) { 82 int v = bel[P.x]; 83 dp[u] = max(dp[u], bit1.sum(v) + P.w); 84 } 85 //printf("u %d dp %lld\n", u, dp[u]); 86 } 87 int dfs1(int l, int r) { 88 int i = Qmax(l, r); 89 int cur = ++K; 90 //printf("cur %d %d %d\n", cur, l, r); 91 bel[i] = cur; 92 mxi[cur] = i; 93 repL[cur] = l; 94 repR[cur] = r; 95 pair<int, int> P; 96 while ((P = Query(1, 1, n, l, r)).first > A[i]) { 97 np[cur].push_back(pts[P.second].back()); 98 //printf("P %d %d\n", P.first, P.second); 99 pts[P.second].pop_back(); 100 Modify(1, 1, n, P.second); 101 } 102 int nl, nr; 103 nl = l; 104 nr = i - 1; 105 if (nl <= nr) G[cur].push_back(dfs1(nl, nr)); 106 nl = i + 1; 107 nr = r; 108 if (nl <= nr) G[cur].push_back(dfs1(nl, nr)); 109 dR[cur] = K; 110 return cur; 111 } 112 int main() { 113 scanf("%d", &n); 114 for (int i = 1; i <= n; i++) scanf("%d", &A[i]), ST[0][i] = i; 115 lg2[0] = -1; 116 for (int i = 1; i <= n; i++) lg2[i] = lg2[i - 1] + ((i & (i - 1)) ? 0 : 1); 117 for (int i = 1; i < 18; i++) { 118 for (int j = 1; j <= n - (1 << i) + 1; j++) { 119 if (A[ST[i - 1][j]] >= A[ST[i - 1][j + (1 << (i - 1))]]) 120 ST[i][j] = ST[i - 1][j]; 121 else 122 ST[i][j] = ST[i - 1][j + (1 << (i - 1))]; 123 } 124 } 125 scanf("%d", &m); 126 long long totsum = 0; 127 for (int i = 1; i <= m; i++) { 128 Node pt; 129 scanf("%d%d%d", &pt.x, &pt.y, &pt.w); 130 pts[pt.x].push_back(pt); 131 totsum += pt.w; 132 } 133 for (int i = 1; i <= n; i++) sort(pts[i].begin(), pts[i].end(), [](Node a, Node b) { 134 return a.y < b.y; 135 }); 136 build(1, 1, n); 137 K = 0; 138 dfs1(1, n); 139 bit1.Init(K); 140 dfs2(1); 141 printf("%lld\n", totsum - dp[1]); 142 return 0; 143 }
LOJ3278. 「JOISC 2020 Day3」收獲
題意
有 $n$ 個員工,$m$ 棵樹種在湖岸。湖的周長為 $L$。一開始員工都站在湖岸的一些不同的位置處,所有樹都種在湖岸的一些不同的位置處(初始沒有樹和人重合),這些位置已經給定。
每棵樹最多會長出一個蘋果,收獲后 $C$ 秒就會再長出個新的。在時刻 $0$,所有樹上都有一個蘋果。員工從時刻 $0$ 開始從各自的地點以 $1$ 的速度順時針前進。一旦遇到成熟的蘋果就將其摘下(摘取時間不計),如果到達時剛長出蘋果也要摘。
現在有 $Q$ 次詢問,每次詢問在時刻 $T$ 結束時員工 $V$ 摘到的蘋果數。
$1\leq n,m\leq 2\times 10^5, n+m\leq L\leq 10^9, 1\leq C\leq 10^9, 1\leq Q\leq 2\times 10^5, T\leq 10^{18}$
題解
看作是樹在逆時針移動,人不動。
那么可以發現,在樹遇到第一個人之后,走至少 $C$ 的位置后遇到的下一個人是接下來摘蘋果的那一個。由於 $C$ 是不變的,因此每個人對應的下一個摘蘋果的人也是確定的,可以構成一棵基環樹森林。
考慮詢問。對於樹的部分,每個詢問只需要求出在 $V$ 的子樹內的、到達 $V$ 的時候時間 $\leq T$ 的蘋果樹個數。這是個二維數點。
對於環的部分,相當於我們要在所有能夠在時間 $T$ 內到達環的蘋果樹中求貢獻,貢獻形如 $\lfloor\frac{X-Y}{Z}\rfloor$,其中 $X,Z$ 的值只與環或者環上的點有關($Z$ 就是環長),$Y$ 的值只與蘋果樹的信息有關。這種整除的貢獻,可以考慮把模 $Z$ 的余數部分去掉變成直接除法,另外應該還要分大於和小於 $X \bmod Z$ 考慮。由於我們只考慮能夠在時間 $T$ 內到達環的蘋果樹,不難想到用可持久化線段樹維護這個東西。
由於這個除法的被除數有爆 long long 的可能,因此需要在一些地方用 __int128。
時間復雜度 $O(n\log n)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 //Fast IO start 4 namespace io { 5 const int BUFSIZE = 1 << 20; 6 char ibuf[BUFSIZE], *is = ibuf, *it = ibuf; 7 char obuf[BUFSIZE], *os = obuf, *ot = obuf + BUFSIZE - 1; 8 inline void read_char(char &c) { 9 if (is == it) { 10 it = (is = ibuf) + fread(ibuf, 1, BUFSIZE, stdin); 11 if (is == it) *it++ = EOF; 12 } 13 c = *is++; 14 } 15 template <class T> 16 inline void read_int(T &x) { 17 T f = 1; 18 char c; 19 read_char(c); 20 while (!isdigit(c)) { 21 if (c == '-') { 22 f = -1; 23 } 24 read_char(c); 25 } 26 x = 0; 27 while (isdigit(c)) { 28 x = x * 10 + c - '0'; 29 read_char(c); 30 } 31 x *= f; 32 } 33 inline void flush() { 34 fwrite(obuf, 1, os - obuf, stdout); 35 os = obuf; 36 } 37 inline void print_char(char c) { 38 *os++ = c; 39 if (os == ot) { 40 flush(); 41 } 42 } 43 template <class T> 44 inline void print_int(T x) { 45 static char q[40]; 46 if (!x) { 47 print_char('0'); 48 } else { 49 if (x < 0) { 50 print_char('-'); 51 x = -x; 52 } 53 int top = 0; 54 while (x) { 55 q[top++] = x % 10 + '0'; 56 x /= 10; 57 } 58 while (top--) { 59 print_char(q[top]); 60 } 61 } 62 } 63 struct flusher_t { 64 inline ~flusher_t() { 65 flush(); 66 } 67 } flusher; 68 } 69 using io::read_char; 70 using io::read_int; 71 using io::print_char; 72 using io::print_int; 73 //Fast IO end 74 const int N = 200005; 75 struct BIT { 76 int n; 77 long long bit[N]; 78 inline void Init(int _n) { 79 n = _n; 80 for (int i = 1; i <= n; i++) bit[i] = 0; 81 } 82 inline void add(int i, long long x) { 83 while (i <= n) { 84 bit[i] += x; 85 i += i & -i; 86 } 87 } 88 inline long long sum(int i) { 89 long long res = 0; 90 while (i > 0) { 91 res += bit[i]; 92 i -= i & -i; 93 } 94 return res; 95 } 96 } bit; 97 int n, m, Q, par[N], g[N], fr_rt[N]; 98 long long L, C, parlen[N], dep[N], f[N], A[N * 2], B[N], ans[N], ps[N]; 99 int dsu[N]; 100 int Find(int x) { 101 return x == dsu[x] ? x : dsu[x] = Find(dsu[x]); 102 } 103 vector<pair<long long, int> > buck[N]; 104 vector<int> tt[N]; 105 vector<int> roots; 106 vector<int> G[N]; 107 inline void Pre_calc() { 108 for (int i = n + 1, j = 1; i <= n * 2; i++) { 109 while (j < i && A[i] - A[j + 1] >= C % L) j++; 110 par[i - n] = j <= n ? j : j - n; 111 parlen[i - n] = (C / L) * L + A[i] - A[j]; 112 } 113 int i = 1, j = 1; 114 while (i <= n && j <= m) { 115 if (A[i] < B[j]) { 116 i++; 117 } else { 118 g[j] = (i > 1 ? i - 1 : n); 119 f[j] = (B[j] - A[g[j]] + L) % L; 120 j++; 121 } 122 } 123 while (j <= m) { 124 g[j] = n; 125 f[j] = (B[j] - A[g[j]] + L) % L; 126 j++; 127 } 128 for (int i = 1; i <= n; i++) { 129 dsu[i] = i; 130 } 131 for (int i = 1; i <= n; i++) { 132 if (Find(i) != Find(par[i])) { 133 dsu[Find(i)] = Find(par[i]); 134 G[par[i]].push_back(i); 135 } else { 136 roots.push_back(i); 137 } 138 } 139 } 140 int dfc, dL[N], dR[N]; 141 void dfs(int u, long long depth) { 142 dL[u] = ++dfc; 143 dep[u] = depth; 144 for (int v : G[u]) fr_rt[v] = fr_rt[u], dfs(v, depth + parlen[v]); 145 dR[u] = dfc; 146 } 147 struct Point { 148 long long x; 149 int y; 150 int id; 151 }; 152 vector<Point> pts; 153 struct Node { 154 int lson, rson, cnt; 155 __int128 sum; 156 inline Node() { lson = rson = cnt = 0; sum = 0; } 157 } tr[N * 19]; 158 int tr_rt[N], cnt_tr; 159 void Ins(int &x, int y, int l, int r, int pos, long long v) { 160 x = ++cnt_tr; 161 tr[x] = tr[y]; 162 tr[x].cnt++; 163 tr[x].sum += v; 164 if (l == r) return; 165 int mid = (l + r) >> 1; 166 if (pos <= mid) Ins(tr[x].lson, tr[y].lson, l, mid, pos, v); 167 else Ins(tr[x].rson, tr[y].rson, mid + 1, r, pos, v); 168 } 169 pair<__int128, int> getsum(int x, int l, int r, int lf, int rg) { 170 if (!x) return make_pair(0, 0); 171 if (lf <= l && r <= rg) return make_pair(tr[x].sum, tr[x].cnt); 172 int mid = (l + r) >> 1; 173 pair<__int128, int> P1(0, 0), P2(0, 0); 174 if (lf <= mid) P1 = getsum(tr[x].lson, l, mid, lf, rg); 175 if (rg > mid) P2 = getsum(tr[x].rson, mid + 1, r, lf, rg); 176 return make_pair(P1.first + P2.first, P1.second + P2.second); 177 } 178 int main() { 179 //freopen("input.txt", "r", stdin); 180 read_int(n); 181 read_int(m); 182 read_int(L); 183 read_int(C); 184 for (int i = 1; i <= n; i++) read_int(A[i]), A[i + n] = A[i] + L; 185 for (int i = 1; i <= m; i++) read_int(B[i]); 186 Pre_calc(); 187 dfc = 0; 188 for (int rt : roots) fr_rt[rt] = rt, dfs(rt, 0); 189 for (int j = 1; j <= m; j++) { 190 pts.push_back((Point){f[j] + dep[g[j]], dL[g[j]], 0}); 191 tt[fr_rt[g[j]]].push_back(j); 192 } 193 read_int(Q); 194 for (int i = 1; i <= Q; i++) { 195 int u; 196 long long T; 197 read_int(u); 198 read_int(T); 199 buck[u].push_back(make_pair(T, i)); 200 pts.push_back((Point){dep[u] + T, u, i}); 201 ans[i] = 0; 202 } 203 sort(pts.begin(), pts.end(), [](Point a, Point b) { 204 if (a.x != b.x) return a.x < b.x; 205 return a.id < b.id; 206 }); 207 bit.Init(n); 208 for (auto &P : pts) { 209 if (!P.id) { 210 bit.add(P.y, 1); 211 } else { 212 int u = P.y; 213 ans[P.id] += bit.sum(dR[u]) - bit.sum(dL[u] - 1); 214 } 215 } 216 for (int rt : roots) { 217 vector<int> us; 218 int u = par[rt]; 219 long long len = parlen[rt]; 220 while (u != rt) { 221 us.push_back(u); 222 ps[u] = len; 223 len += parlen[u]; 224 u = par[u]; 225 } 226 us.push_back(rt); 227 ps[rt] = len; 228 vector<long long> vals, mods; 229 for (int j : tt[rt]) { 230 long long val = dep[g[j]] + f[j], rem = val % len; 231 vals.push_back(val); 232 mods.push_back(rem); 233 } 234 sort(vals.begin(), vals.end()); 235 sort(mods.begin(), mods.end()); 236 mods.resize(unique(mods.begin(), mods.end()) - mods.begin()); 237 int cnt = 0; 238 tr_rt[0] = 0; 239 cnt_tr = 0; 240 for (long long val : vals) { 241 int pos = lower_bound(mods.begin(), mods.end(), val % len) - mods.begin() + 1; 242 cnt++; 243 Ins(tr_rt[cnt], tr_rt[cnt - 1], 1, (int)mods.size(), pos, val - val % len); 244 } 245 for (int u : us) { 246 for (auto Que : buck[u]) { 247 long long T = Que.first - ps[u] + len; 248 int r = upper_bound(vals.begin(), vals.end(), Que.first - ps[u]) - vals.begin(); 249 int pos = upper_bound(mods.begin(), mods.end(), T % len) - mods.begin(); 250 if (pos >= 1) { 251 pair<__int128, int> P = getsum(tr_rt[r], 1, (int)mods.size(), 1, pos); 252 __int128 s = P.first; 253 int cc = P.second; 254 __int128 tmp = T - T % len; 255 tmp = tmp * cc; 256 tmp -= s; 257 tmp /= len; 258 ans[Que.second] += (long long)tmp; 259 } 260 if (pos <= (int)mods.size()) { 261 pair<__int128, int> P = getsum(tr_rt[r], 1, (int)mods.size(), pos + 1, (int)mods.size()); 262 __int128 s = P.first; 263 int cc = P.second; 264 __int128 tmp = T - T % len - len; 265 tmp = tmp * cc; 266 tmp -= s; 267 tmp /= len; 268 ans[Que.second] += (long long)tmp; 269 } 270 } 271 } 272 for (int i = 1; i <= cnt_tr; i++) tr[i] = Node(); 273 } 274 for (int i = 1; i <= Q; i++) print_int(ans[i]), print_char('\n'); 275 return 0; 276 }
LOJ3279. 「JOISC 2020 Day3」迷路的貓
(LOJ 暫不支持評測通信題,可以在 UOJ 上測)
題意
這是一道通信題。
有一張 $n$ 個點 $m$ 條邊的連通無向圖,給了代碼 A。點和邊的編號都從 $0$ 開始。現在代碼 A 可以給這 $m$ 條邊做標記,一共有 $A$ 種標記:$0..A-1$。每條邊都要做其中一個標記。
然后,代碼 B 會被系統放在其中一個非零的點中。但 B 是個路痴,並不知道這是哪一個點,它只能知道對於每種標記 $i$,與這個點相連的邊有 $y[i]$ 條是標記 $i$。B 會選擇一個標記並走進這個標記的其中一條邊中。(具體走的邊由系統指定,不一定是均勻隨機的。)另外,如果已經走了至少一步,B 可以直接弄清楚哪條邊是剛剛走的,即這條邊不需要通過標記來辨認(方便起見,我們不會把這條邊放在 $y[i]$ 的統計中去)。所以 B 除了選擇一個標記走以外,也可以選擇剛剛走過的那條邊,並走回去。B 的目標是走到點 $0$。
當然,由於標記種類很少,所以我們允許 B 走一些彎路。設從 $0$ 到 B 的起點 $S$ 的最短路為 $d$,那么你要保證 B 在走不超過 $d+B$ 步后能夠走到點 $0$。其中 $B$ 是個給定的非負整數。
請實現代碼 A 和 B,讓 A 對邊做標記,讓 B 在標記的圖上走,使得滿足上述條件。
對於所有數據,有 $2\leq n\leq 20000, 1\leq m\leq 20000$,圖保證連通,無重邊自環。
子任務有兩類,如下(我們都取兩類中的最嚴格限制):
- $A=3, B=0$($15$ 分)
- $A=2, B=6, m=n-1$($85$ 分)
題解
對於第一類子任務。我們只能走最短路。
考慮求出這張圖的 BFS 樹,顯然,所有邊要么是連接相鄰兩層的點(異層邊),要么是連接同層的點(同層邊)。
如果沒有同層邊,那么我們只要對於每一層的邊做同一種標記,按 $0,1,2$ 循環標記即可。因為這樣的話,在某一個點上,即使有兩種存在的標記,我們也可以確定哪一個是往上走的。
那么如果有了同層邊,其實也很簡單,只要做它下面的那一層的邊的標記即可。如下圖所示。
這樣我們依舊可以確定哪一條邊是向上的。
對於第二類子任務,只有兩種標記,但圖是一個樹,而且允許多走六步。
我們先考慮把樹划分為若干條鏈,如果一個點有至少兩個兒子,那么稱之為分叉點。我們希望標記能夠滿足:
- 對於每個分叉點,到父親的邊和到兒子的邊的標記不同。
- 每一條鏈上都是 $110010$ 的循環位移。
那么對於代碼 B,它先走三步,如果中途走到非鏈的部分就直接確定了方向。否則,它還在鏈上,而且可以確定周圍長度為 $5$ 的子串的樣子,這樣的話,一定可以確定它是在往上走還是往下走,這樣也確定了方向,而且最多多走 $6$ 步。
細節有點多,寫的時候要注意。
代碼
代碼 A(Anthony.cpp):

1 #include "Anthony.h" 2 #include <bits/stdc++.h> 3 using namespace std; 4 vector<int> Mark(int n, int m, int A, int B, vector<int> U, vector<int> V) { 5 vector<int> dist(n, -1); 6 vector<vector<int> > G(n); 7 for (int i = 0; i < m; i++) { 8 G[U[i]].push_back(V[i]); 9 G[V[i]].push_back(U[i]); 10 } 11 dist[0] = 0; 12 queue<int> que; 13 que.push(0); 14 while (!que.empty()) { 15 int u = que.front(); que.pop(); 16 for (int v : G[u]) { 17 if (dist[v] == -1) { 18 dist[v] = dist[u] + 1; 19 que.push(v); 20 } 21 } 22 } 23 vector<int> ret(m); 24 if (A >= 3) { 25 for (int i = 0; i < m; i++) { 26 ret[i] = min(dist[U[i]], dist[V[i]]) % 3; 27 } 28 } else { 29 const int arr[6] = {1, 1, 0, 0, 1, 0}; 30 for (int i = 0; i < n; i++) G[i].clear(); 31 for (int i = 0; i < m; i++) { 32 if (dist[U[i]] > dist[V[i]]) swap(U[i], V[i]); 33 G[U[i]].push_back(i); 34 } 35 function<void(int, int)> dfs = [&](int u, int fr) { 36 if ((int)G[u].size() == 1) { 37 int p; 38 if (fr == -1 || ret[fr] == 0) { 39 p = 0; 40 } else { 41 p = 2; 42 } 43 while ((int)G[u].size() == 1) { 44 int i = G[u][0]; 45 int v = V[i]; 46 u = v; 47 fr = i; 48 ret[fr] = arr[p]; 49 p = (p + 1) % 6; 50 } 51 } 52 int c = (fr == -1 ? 1 : ret[fr] ^ 1); 53 for (int i : G[u]) { 54 int v = V[i]; 55 ret[i] = c; 56 dfs(v, i); 57 } 58 }; 59 dfs(0, -1); 60 } 61 return ret; 62 }
代碼 B(Catherine.cpp):

1 #include "Catherine.h" 2 #include <bits/stdc++.h> 3 using namespace std; 4 namespace { 5 const int arr[6] = {1, 1, 0, 0, 1, 0}; 6 int A, on, fir, last; 7 vector<int> vec; 8 vector<int> cs; 9 inline bool bad(vector<int> v) { 10 vector<int> al; 11 for (int i = 0; i < 6; i++) al.push_back(arr[i]); 12 for (int i = 0; i < 6; i++) al.push_back(arr[i]); 13 for (int i = 0; i < 8; i++) { 14 int flag = 1; 15 for (int j = 0; j < 5; j++) if (al[i + j] != v[j]) { 16 flag = 0; 17 break; 18 } 19 if (flag) return true; 20 } 21 return false; 22 } 23 } 24 void Init(int _A, int B) { 25 A = _A; 26 on = 0; 27 fir = 1; 28 last = -1; 29 } 30 int Move(vector<int> y) { 31 if (A >= 3) { 32 int cc = 0; 33 for (int i = 0; i < 3; i++) cc += (y[i] != 0 ? 1 : 0); 34 if (cc == 1) { 35 if (y[0]) return 0; 36 else if (y[1]) return 1; 37 else return 2; 38 } else { 39 if (y[0] && y[1]) return 0; 40 else if (y[1] && y[2]) return 1; 41 else return 2; 42 } 43 } else { 44 if (on) { 45 if (y[0] == 1 && y[1] != 1) return last = 0; 46 else if (y[0] != 1 && y[1] == 1) return last = 1; 47 else return last ^= 1; 48 } 49 if (fir) { 50 fir = 0; 51 if (y[0] != 1 && y[1] != 1) { 52 if (y[0]) last = 0; 53 else last = 1; 54 y[last]--; 55 cs = y; 56 vec.push_back(last); 57 return last; 58 } 59 if (y[0] == 1) last = 0; 60 else last = 1; 61 if (y[last ^ 1] != 1) { 62 on = 1; 63 return last; 64 } 65 y[last]--; 66 cs = y; 67 vec.push_back(last); 68 return last; 69 } 70 if (y[0] != 1 && y[1] != 1) { 71 on = 1; 72 return -1; 73 } 74 if (y[0] == 1 && y[1] == 1) { 75 on = 1; 76 return last ^= 1; 77 } 78 if (y[0] > 1) { 79 on = 1; 80 return last = 1; 81 } 82 if (y[1] > 1) { 83 on = 1; 84 return last = 0; 85 } 86 if (vec.size() < 3) { 87 if (y[0]) last = 0; 88 else last = 1; 89 vec.push_back(last); 90 return last; 91 } 92 vector<int> v; 93 v.push_back(cs[0] ? 0 : 1); 94 for (int i = 0; i < 3; i++) { 95 v.push_back(vec[i]); 96 } 97 v.push_back(y[0] ? 0 : 1); 98 on = 1; 99 if (bad(v)) { 100 return -1; 101 } else { 102 return last = v.back(); 103 } 104 } 105 }
LOJ3280. 「JOISC 2020 Day4」首都城市
題意
給定一棵 $n$ 個節點的樹,每個節點 $i$ 會屬於某一座城市 $C_i$。一共有 $k$ 座城市,保證每一座都有管轄的節點(即每一座城市都非空)。
現在想要在這 $k$ 座城市中選出一個作為首都。為了安全,要求首都城市管轄的節點的導出子圖必須是一個連通塊。
不過可能並沒有這樣的城市,因此國王打算合並一些城市,使得存在至少一座滿足上述條件的城市。
每次只能合並兩座城市,而且合並一次會花費 $1$ 的代價。求達到目標的最小代價。
$1\leq k\leq n\leq 2\times 10^5$
題解
對於每座城市,如果它作為了最終首都的一部分,那么它有可能要依賴某些其他的城市(即選了它就必須選一些其他的城市)。
具體來說,把這座城市的點集的虛樹建出來,那么虛樹的邊上的所有點所在的城市都是要依賴的。(事實上並不需要建出虛樹那么麻煩,其實把所有點的 LCA 取出來,然后把每個點到 LCA 的路徑取出來即可。)
這樣依賴的邊數可能會很多,因此使用數據結構優化建圖。我們考慮使用倍增來優化,這樣每條路徑會連 $O(\log n)$ 條依賴關系的邊。建出的依賴圖中,共有 $O(n\log n)$ 個點和 $O(n\log n)$ 條邊,可以接受。
然后對這個依賴圖進行強連通縮點,得到一個 DAG,其中 DAG 上每個點會帶權(帶的就是里面包含的城市個數)。那么在這個 DAG 中,不依賴其他點的點就是答案的備選(否則肯定不是最優的),從中選取權值最小的那個即可。
時間復雜度 $O(n\log n)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 //Fast IO start 4 namespace io { 5 const int BUFSIZE = 1 << 20; 6 char ibuf[BUFSIZE], *is = ibuf, *it = ibuf; 7 char obuf[BUFSIZE], *os = obuf, *ot = obuf + BUFSIZE - 1; 8 inline void read_char(char &c) { 9 if (is == it) { 10 it = (is = ibuf) + fread(ibuf, 1, BUFSIZE, stdin); 11 if (is == it) *it++ = EOF; 12 } 13 c = *is++; 14 } 15 template <class T> 16 inline void read_int(T &x) { 17 T f = 1; 18 char c; 19 read_char(c); 20 while (!isdigit(c)) { 21 if (c == '-') { 22 f = -1; 23 } 24 read_char(c); 25 } 26 x = 0; 27 while (isdigit(c)) { 28 x = x * 10 + c - '0'; 29 read_char(c); 30 } 31 x *= f; 32 } 33 inline void flush() { 34 fwrite(obuf, 1, os - obuf, stdout); 35 os = obuf; 36 } 37 inline void print_char(char c) { 38 *os++ = c; 39 if (os == ot) { 40 flush(); 41 } 42 } 43 template <class T> 44 inline void print_int(T x) { 45 static char q[40]; 46 if (!x) { 47 print_char('0'); 48 } else { 49 if (x < 0) { 50 print_char('-'); 51 x = -x; 52 } 53 int top = 0; 54 while (x) { 55 q[top++] = x % 10 + '0'; 56 x /= 10; 57 } 58 while (top--) { 59 print_char(q[top]); 60 } 61 } 62 } 63 struct flusher_t { 64 inline ~flusher_t() { 65 flush(); 66 } 67 } flusher; 68 } 69 using io::read_char; 70 using io::read_int; 71 using io::print_char; 72 using io::print_int; 73 //Fast IO end 74 const int N = 200002; 75 int n, k, tot, dfc, C[N], dfn[N * 20], low[N * 20], dep[N], pa[18][N], id[18][N]; 76 vector<int> G[N], buck[N], H[N * 20]; 77 inline void add_edge(int u, int v) { 78 H[u].push_back(v); 79 } 80 inline int LCA(int u, int v) { 81 if (dep[u] < dep[v]) swap(u, v); 82 for (int i = 0; i < 18; i++) if ((dep[u] - dep[v]) >> i & 1) u = pa[i][u]; 83 if (u == v) return u; 84 for (int i = 17; ~i; i--) if (pa[i][u] != pa[i][v]) u = pa[i][u], v = pa[i][v]; 85 return pa[0][u]; 86 } 87 void dfs(int u, int lst, int depth) { 88 dfn[u] = ++dfc; 89 pa[0][u] = lst; 90 id[0][u] = ++tot; 91 add_edge(id[0][u], u); 92 if (lst) add_edge(id[0][u], lst); 93 dep[u] = depth; 94 for (int v : G[u]) if (v != lst) { 95 dfs(v, u, depth + 1); 96 } 97 } 98 inline void gao(int u, int v, int z) { 99 for (int i = 0; i < 18; i++) if ((dep[v] - dep[u]) >> i & 1) { 100 add_edge(z, id[i][v]); 101 v = pa[i][v]; 102 } 103 } 104 int stk[N * 20], tp, instk[N * 20], sw[N * 20], bel[N * 20], scc, deg[N * 20]; 105 void Tarjan(int u) { 106 dfn[u] = low[u] = ++dfc; 107 stk[++tp] = u; 108 instk[u] = 1; 109 for (int v : H[u]) { 110 if (!dfn[v]) { 111 Tarjan(v); 112 low[u] = min(low[u], low[v]); 113 } else if (instk[v]) { 114 low[u] = min(low[u], dfn[v]); 115 } 116 } 117 if (dfn[u] == low[u]) { 118 sw[++scc] = 0; 119 int w; 120 do { 121 w = stk[tp--]; 122 bel[w] = scc; 123 instk[w] = 0; 124 if (w > tot) sw[scc]++; 125 } while (w != u); 126 } 127 } 128 int main() { 129 read_int(n); 130 read_int(k); 131 for (int i = 1; i < n; i++) { 132 int u, v; 133 read_int(u); 134 read_int(v); 135 G[u].push_back(v); 136 G[v].push_back(u); 137 } 138 for (int i = 1; i <= n; i++) { 139 read_int(C[i]); 140 buck[C[i]].push_back(i); 141 } 142 tot = n; 143 dfc = 0; 144 dfs(1, 0, 0); 145 for (int i = 1; i < 18; i++) { 146 for (int u = 1; u <= n; u++) { 147 pa[i][u] = pa[i - 1][pa[i - 1][u]]; 148 id[i][u] = ++tot; 149 add_edge(id[i][u], id[i - 1][u]); 150 if (pa[i - 1][u]) add_edge(id[i][u], id[i - 1][pa[i - 1][u]]); 151 } 152 } 153 for (int i = 1; i <= n; i++) { 154 add_edge(i, tot + C[i]); 155 } 156 for (int i = 1; i <= k; i++) { 157 sort(buck[i].begin(), buck[i].end(), [](int u, int v) { 158 return dfn[u] < dfn[v]; 159 }); 160 tp = 0; 161 for (int u : buck[i]) { 162 if (!tp) { stk[++tp] = u; continue; } 163 int lca = LCA(u, stk[tp]); 164 while (dfn[lca] < dfn[stk[tp]]) { 165 if (dfn[lca] >= dfn[stk[tp - 1]]) { 166 gao(lca, stk[tp], i + tot); 167 if (stk[--tp] != lca) stk[++tp] = lca; 168 break; 169 } 170 gao(stk[tp - 1], stk[tp], i + tot); 171 tp--; 172 } 173 stk[++tp] = u; 174 } 175 while (tp > 1) gao(stk[tp - 1], stk[tp], i + tot), tp--; 176 } 177 memset(dfn, 0, sizeof(dfn)); 178 scc = 0; 179 for (int i = 1; i <= tot + k; i++) { 180 if (!dfn[i]) { 181 dfc = tp = 0; 182 Tarjan(i); 183 } 184 } 185 for (int i = 1; i <= tot + k; i++) { 186 for (int j : H[i]) { 187 if (bel[i] != bel[j]) { 188 deg[bel[i]]++; 189 } 190 } 191 } 192 int ans = 0x3f3f3f3f; 193 for (int i = 1; i <= scc; i++) { 194 if (!deg[i]) { 195 ans = min(ans, sw[i]); 196 } 197 } 198 ans--; 199 assert(ans < k && ans >= 0); 200 print_int(ans); 201 print_char('\n'); 202 return 0; 203 }
LOJ3281. 「JOISC 2020 Day4」傳奇團子師傅
題意
這是一道提交答案題。
你有若干個團子和竹簽,團子被放在一個 $n$ 行 $m$ 列的格子里,每個格子恰好有一個團子。團子有三種顏色:P, W, G。
你每次會選擇三個連續的團子,並把它們串在竹簽上。這三個團子必須是沿着豎直方向或水平方向或對角方向連續的。一串團子是漂亮的當且僅當三個團子的顏色依次為 P-W-G 或 G-W-P。每個團子最多只能串在一根竹簽上。你想要得到盡可能多串漂亮的團子。
一共有六個測試點,會給出評分參數,具體見 OJ。當你的答案中漂亮的團子數量 $\geq Z$ 時,這個測試點就算滿分。
題解
(這題的模擬退火的一些內容參考了神仙 PinkRabbit 在 CF 上的評論)
把所有可能的團子串都取出來,如果兩個團子串不可能同時出現(即它們所用的團子有重復),那么就在它們之間連一條邊。這樣我們構建出一張圖。
我們就是要找這張圖的一個大小盡可能大的獨立集。
考慮模擬退火。初始所有點都沒選進獨立集,每次隨機一個不在獨立集內的點 $u$,然后試圖把它加進獨立集。當然加的同時要從集合中去掉與它相鄰的點。
就像常見的模擬退火一樣,如果新的解優於舊的解或者以一定的概率接受一個較劣的解,那么就把新的解保留。
不過對於同一個溫度,可能需要多隨機幾個 $u$。而且由於解的大小比較大,溫度的變化率要非常非常接近 $1$,例如 $0.999999$。當然,溫度的初始值不一定要那么大(畢竟你算一個新的解的時間就夠長的了)。
然后跑模擬退火就行了,很快就可以把前 $4$ 個點過掉。然而我第 $5,6$ 個點都差一點點(把分數四舍五入就到 $100$ 了的那種),非常自閉。后來我又把代碼魔改了一通,使得它能夠在一個差一點點的解的基礎上繼續退火。后來換了幾個種子終於把這兩個點過掉了,而且答案正好是 $Z$。(並不知道神仙粉兔是如何退火出來更優的解的)
在跑退火的時候,有時我想要直接用 Ctrl-C 強制讓它停下來,但這樣就不會輸出當前跑出的最優方案了。我的解決方法是定義一個 struct,並給它定義一個析構函數。這樣在程序終止的時候,它會自動執行輸出方案的部分。
代碼
(這份代碼是我在跑第 $5$ 個測試點的時候用的,它會讀入一個較優的解,然后在這個解的基礎上進行退火。僅供參考)

1 #include <bits/stdc++.h> 2 using namespace std; 3 const int dx[8] = {1, 1, 1, 0, 0, -1, -1, -1}; 4 const int dy[8] = {1, 0, -1, 1, -1, 1, 0, -1}; 5 const int val[8] = {3, 1, 2, 0, 0, 2, 1, 3}; 6 const int V = 1000005; 7 int n, m, tot, id[505][505][4]; 8 char a[505][505]; 9 vector<int> G[V]; 10 inline void add_edge(int u, int v) { 11 G[u].push_back(v); 12 } 13 inline void Construct() { 14 /* 15 - 0 16 | 1 17 / 2 18 \ 3 19 */ 20 tot = 0; 21 for (int i = 1; i <= n; i++) { 22 for (int j = 1; j <= m; j++) if (a[i][j] == 'W') { 23 if ((a[i][j - 1] == 'P' && a[i][j + 1] == 'G') || (a[i][j - 1] == 'G' && a[i][j + 1] == 'P')) { 24 id[i][j][0] = ++tot; 25 } 26 if ((a[i - 1][j] == 'P' && a[i + 1][j] == 'G') || (a[i - 1][j] == 'G' && a[i + 1][j] == 'P')) { 27 id[i][j][1] = ++tot; 28 } 29 if ((a[i + 1][j - 1] == 'P' && a[i - 1][j + 1] == 'G') || (a[i + 1][j - 1] == 'G' && a[i - 1][j + 1] == 'P')) { 30 id[i][j][2] = ++tot; 31 } 32 if ((a[i - 1][j - 1] == 'P' && a[i + 1][j + 1] == 'G') || (a[i - 1][j - 1] == 'G' && a[i + 1][j + 1] == 'P')) { 33 id[i][j][3] = ++tot; 34 } 35 for (int k = 0; k < 4; k++) { 36 for (int l = 0; l < 4; l++) if (k != l && id[i][j][k] && id[i][j][l]) { 37 add_edge(id[i][j][k], id[i][j][l]); 38 } 39 } 40 } 41 } 42 for (int i = 1; i <= n; i++) { 43 for (int j = 1; j <= m; j++) if (a[i][j] == 'W') { 44 if (id[i][j][0]) { 45 for (int k = 0; k < 8; k++) { 46 int x = i + dx[k], y = j - 1 + dy[k]; 47 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 48 add_edge(id[i][j][0], id[x][y][val[k]]); 49 } 50 } 51 for (int k = 0; k < 8; k++) { 52 int x = i + dx[k], y = j + 1 + dy[k]; 53 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 54 add_edge(id[i][j][0], id[x][y][val[k]]); 55 } 56 } 57 } 58 if (id[i][j][1]) { 59 for (int k = 0; k < 8; k++) { 60 int x = i - 1 + dx[k], y = j + dy[k]; 61 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 62 add_edge(id[i][j][1], id[x][y][val[k]]); 63 } 64 } 65 for (int k = 0; k < 8; k++) { 66 int x = i + 1 + dx[k], y = j + dy[k]; 67 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 68 add_edge(id[i][j][1], id[x][y][val[k]]); 69 } 70 } 71 } 72 if (id[i][j][2]) { 73 for (int k = 0; k < 8; k++) { 74 int x = i + 1 + dx[k], y = j - 1 + dy[k]; 75 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 76 add_edge(id[i][j][2], id[x][y][val[k]]); 77 } 78 } 79 for (int k = 0; k < 8; k++) { 80 int x = i - 1 + dx[k], y = j + 1 + dy[k]; 81 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 82 add_edge(id[i][j][2], id[x][y][val[k]]); 83 } 84 } 85 } 86 if (id[i][j][3]) { 87 for (int k = 0; k < 8; k++) { 88 int x = i - 1 + dx[k], y = j - 1 + dy[k]; 89 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 90 add_edge(id[i][j][3], id[x][y][val[k]]); 91 } 92 } 93 for (int k = 0; k < 8; k++) { 94 int x = i + 1 + dx[k], y = j + 1 + dy[k]; 95 if (x >= 1 && x <= n && y >= 1 && y <= m && (x != i || y != j) && id[x][y][val[k]]) { 96 add_edge(id[i][j][3], id[x][y][val[k]]); 97 } 98 } 99 } 100 } 101 } 102 } 103 //mt19937 gen(1145141); 104 mt19937 gen(666233555); 105 inline double Rand01() { 106 return (double)gen() / 4294967296; 107 } 108 int Sta[V], Ans[V]; 109 int Cnt, Anscnt; 110 int tr[V * 4]; 111 void build(int i, int l, int r) { 112 if (l == r) { 113 tr[i] = 1 - Sta[l]; 114 return; 115 } 116 int mid = (l + r) >> 1; 117 build(i << 1, l, mid); 118 build(i << 1 | 1, mid + 1, r); 119 tr[i] = tr[i << 1] + tr[i << 1 | 1]; 120 } 121 void modify(int i, int l, int r, int pos, int val) { 122 tr[i] += val; 123 if (l == r) return; 124 int mid = (l + r) >> 1; 125 if (pos <= mid) modify(i << 1, l, mid, pos, val); 126 else modify(i << 1 | 1, mid + 1, r, pos, val); 127 } 128 int query(int i, int l, int r, int pos) { 129 if (l == r) return l; 130 int mid = (l + r) >> 1; 131 if (tr[i << 1] >= pos) return query(i << 1, l, mid, pos); 132 else return query(i << 1 | 1, mid + 1, r, pos - tr[i << 1]); 133 } 134 int Bad[V]; 135 struct Ender_t { 136 inline ~Ender_t() { 137 fprintf(stderr, "%d\n", Anscnt); 138 for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] == 'W') { 139 if (id[i][j][0] && Ans[id[i][j][0]]) a[i][j] = '-'; 140 if (id[i][j][1] && Ans[id[i][j][1]]) a[i][j] = '|'; 141 if (id[i][j][2] && Ans[id[i][j][2]]) a[i][j] = '/'; 142 if (id[i][j][3] && Ans[id[i][j][3]]) a[i][j] = '\\'; 143 } 144 for (int i = 1; i <= n; i++) puts(a[i] + 1); 145 } 146 } Ender; 147 char b[505][505]; 148 int main() { 149 freopen("input_05.txt", "r", stdin); 150 freopen("tmp.txt", "w", stdout); 151 scanf("%d%d", &n, &m); 152 memset(a, 0, sizeof(a)); 153 for (int i = 1; i <= n; i++) { 154 scanf(" %s", a[i] + 1); 155 } 156 Construct(); 157 fprintf(stderr, "%d\n", tot); 158 memset(Sta, 0, sizeof(Sta)); 159 memset(Ans, 0, sizeof(Ans)); 160 freopen("output_05.txt", "r", stdin); 161 for (int i = 1; i <= n; i++) scanf(" %s", b[i] + 1); 162 for (int i = 1; i <= n; i++) { 163 for (int j = 1; j <= m; j++) { 164 if (b[i][j] == '-') Sta[id[i][j][0]] = 1; 165 if (b[i][j] == '|') Sta[id[i][j][1]] = 1; 166 if (b[i][j] == '/') Sta[id[i][j][2]] = 1; 167 if (b[i][j] == '\\') Sta[id[i][j][3]] = 1; 168 } 169 } 170 build(1, 1, tot); 171 memcpy(Ans + 1, Sta + 1, tot << 2); 172 Cnt = Anscnt = tot - tr[1]; 173 const int B = 48620; 174 while (Anscnt < B) { 175 fprintf(stderr, "--- Best %d Current %d\n", Anscnt, Cnt); 176 Bad[Anscnt]++; 177 if (Bad[Anscnt] % 5 == 4) { 178 memcpy(Sta + 1, Ans + 1, tot << 2); 179 Cnt = Anscnt; 180 build(1, 1, tot); 181 } 182 double T = 1000; 183 while (T > 1e-6) { 184 int t = 5; 185 while (t--) { 186 int u = query(1, 1, tot, uniform_int_distribution<int>(1, tot - Cnt)(gen)); 187 int delta = 1; 188 for (int v : G[u]) if (Sta[v]) delta--; 189 int nCnt = Cnt + delta; 190 if (nCnt > Cnt || Rand01() < exp(-1.0 * (Cnt - nCnt) / T)) { 191 Sta[u] = 1; 192 modify(1, 1, tot, u, -1); 193 for (int v : G[u]) if (Sta[v]) Sta[v] = 0, modify(1, 1, tot, v, 1); 194 Cnt = nCnt; 195 if (Anscnt < Cnt) { 196 Anscnt = Cnt; 197 memcpy(Ans + 1, Sta + 1, tot << 2); 198 if (Anscnt >= B) break; 199 } 200 } 201 } 202 if (Anscnt >= B) break; 203 T *= 0.9999996; 204 } 205 } 206 return 0; 207 }
LOJ3282. 「JOISC 2020 Day4」治療計划
題意
(這個題面很切合當下的時代背景(苦笑))
某城市有 $n$ 個人,分別住在位置 $1..n$。最近,某種病毒肆虐了這座城市,且這 $n$ 個人全部感染了這種病毒。
現在有 $m$ 種治療方案,第 $i$ 種是:在第 $T_i$ 天的晚上,把區間 $[L_i, R_i]$ 內的人全部治療好(如果本來就是健康的人就沒有影響),治療的代價是 $C_i$。(同一天可以進行多次治療方案)
但是,這種病毒傳染性很強。具體來說,如果在第 $i$ 天的早上,位置 $x$ 的人感染了病毒,那么在這天的中午,位置 $x-1$ 和 $x+1$ 就會被傳染(如果對應位置不存在就不傳染)。一個人可以多次感染。
所以,消滅這種病毒的唯一方法就是讓這 $n$ 個人都是健康的。求達到這一目標的最小代價,如果無解輸出 $-1$。
$1\leq n\leq 10^9, 1\leq m\leq 10^5, 1\leq T_i\leq 10^9$
題解
這題要的是最優解,我們首先觀察出來一些性質:如果在某次治療時,存在健康人的極長區間被這次治療完全包含,那么這個健康人區間對應的治療是無用的,可以去掉。
這個性質比較顯然。它有一個也很顯然的推論:最優解中,在某次治療時,如果治療區間內部存在健康人區間,那么這個區間要么會向左延伸到治療區間以外,要么會向右延伸到治療區間以外。
所以,當加入一個治療方案時,它會把至多兩個健康人區間合並在一起。
而我們可以發現,健康人區間的邊界只會與某個治療方案有關(左右邊界所屬的治療方案可能不同)。我們按時間順序掃過來,記錄健康人區間的邊界所屬的治療方案,就可以判斷區間的合並了。我們的最終目標就是要在某個時刻讓健康人區間的左右端點分別為 $1, n$。
進一步地,判斷合並的區間其實不需要按照時間順序。只要以任何順序加入治療方案,不斷合並區間,最終合並出來 $[1,n]$ 即可。
所以我們甚至可以從左到右掃區間。那么這樣我們考慮這樣一個最短路(dp)建圖。所有左端點為 $1$ 的治療方案 $i$ 是起點,距離就是其代價 $C_i$。
從點 $i$ 到點 $j$ 有邊,當且僅當:
- $T_j \geq T_i$ 且 $R_i-T_j+T_i \geq L_j-1$,或者
- $T_j < T_i$ 且 $L_j+T_i-T_j \leq R_i+1$
邊權就是 $C_j$。
跑完最短路后,對於所有右端點為 $n$ 的治療方案,將他們的最短路取個 $\min$ 就是答案了。
考慮優化,顯然可以按 $T_i$ 的順序建出兩棵可持久化線段樹,然后在可持久化線段樹上連邊、跑最短路即可。
時間復雜度 $O(m\log^2 m)$。
代碼

1 #include <bits/stdc++.h> 2 using namespace std; 3 const int M = 100005; 4 int n, m, T[M], L[M], R[M], C[M], id[M]; 5 priority_queue<pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > que; 6 vector<int> v1, v2; 7 vector<pair<int, int> > G[M * 37]; 8 long long dist[M * 37]; 9 inline void add_edge(int u, int v, int w) { 10 G[u].emplace_back(v, w); 11 } 12 struct Node { 13 int lson, rson; 14 } tr[M * 18 * 2]; 15 int tot, root2[M], root1[M]; 16 void Ins(int &x, int y, int l, int r, int pos, int id) { 17 x = ++tot; 18 tr[x] = tr[y]; 19 if (y) add_edge(x + m, y + m, 0); 20 if (l == r) { 21 add_edge(x + m, id, C[id]); 22 return; 23 } 24 int mid = (l + r) >> 1; 25 if (pos <= mid) Ins(tr[x].lson, tr[y].lson, l, mid, pos, id), add_edge(x + m, tr[x].lson + m, 0); 26 else Ins(tr[x].rson, tr[y].rson, mid + 1, r, pos, id), add_edge(x + m, tr[x].rson + m, 0); 27 } 28 void Add(int x, int l, int r, int lf, int rg, int id) { 29 if (!x) return; 30 if (lf <= l && r <= rg) { 31 add_edge(id, x + m, 0); 32 return; 33 } 34 int mid = (l + r) >> 1; 35 if (lf <= mid) Add(tr[x].lson, l, mid, lf, rg, id); 36 if (rg > mid) Add(tr[x].rson, mid + 1, r, lf, rg, id); 37 } 38 int main() { 39 scanf("%d%d", &n, &m); 40 for (int i = 1; i <= m; i++) { 41 scanf("%d%d%d%d", &T[i], &L[i], &R[i], &C[i]); 42 v1.push_back(L[i] + T[i] - 1); 43 v2.push_back(L[i] - T[i] - 1); 44 id[i] = i; 45 } 46 sort(id + 1, id + 1 + m, [](int i, int j) { 47 return T[i] < T[j]; 48 }); 49 sort(v1.begin(), v1.end()); 50 v1.resize(unique(v1.begin(), v1.end()) - v1.begin()); 51 sort(v2.begin(), v2.end()); 52 v2.resize(unique(v2.begin(), v2.end()) - v2.begin()); 53 root2[0] = 0; 54 for (int i = 1; i <= m; i++) { 55 int j = id[i]; 56 int p = lower_bound(v2.begin(), v2.end(), L[j] - T[j] - 1) - v2.begin() + 1; 57 Ins(root2[i], root2[i - 1], 1, m, p, j); 58 } 59 root1[m + 1] = 0; 60 for (int i = m; i; i--) { 61 int j = id[i]; 62 int p = lower_bound(v1.begin(), v1.end(), L[j] + T[j] - 1) - v1.begin() + 1; 63 Ins(root1[i], root1[i + 1], 1, m, p, j); 64 } 65 for (int i = 1, k = 1; i <= m; i++) { 66 while (k <= m && T[id[i]] >= T[id[k]]) k++; 67 int j = id[i]; 68 int p = upper_bound(v2.begin(), v2.end(), R[j] - T[j]) - v2.begin(); 69 if (1 <= p) Add(root2[k - 1], 1, m, 1, p, j); 70 } 71 for (int i = m, k = m; i; i--) { 72 while (k > 0 && T[id[i]] <= T[id[k]]) k--; 73 int j = id[i]; 74 int p = upper_bound(v1.begin(), v1.end(), R[j] + T[j]) - v1.begin(); 75 if (1 <= p) Add(root1[k + 1], 1, m, 1, p, j); 76 } 77 memset(dist, 0x3f, sizeof(dist)); 78 for (int i = 1; i <= m; i++) { 79 if (L[i] == 1) { 80 dist[i] = C[i]; 81 que.push(make_pair(dist[i], i)); 82 } 83 } 84 while (!que.empty()) { 85 auto P = que.top(); que.pop(); 86 int u = P.second; 87 if (dist[u] != P.first) continue; 88 for (auto &e : G[u]) { 89 int v = e.first; 90 if (dist[v] > dist[u] + e.second) { 91 dist[v] = dist[u] + e.second; 92 que.push(make_pair(dist[v], v)); 93 } 94 } 95 } 96 long long ans = 1ll << 55; 97 for (int i = 1; i <= m; i++) { 98 if (R[i] == n) ans = min(ans, dist[i]); 99 } 100 if (ans >= (1ll << 55)) ans = -1; 101 printf("%lld\n", ans); 102 return 0; 103 }