题解:P4246 [SHOI2008] 堵塞的交通

· · 题解

来顺一下思路?

本篇题解的定位和目的是尽笔者所能帮助大家搞清楚题解区“考虑线段树”这句话背后的逻辑和原因。

换句话说,本题解重点在于理清思路线的来历和去向,而不在于解释线段树 pushup() 该怎么维护。

个人建议先去阅读其他题解,在“明白如何做但不知道为什么这么做”的时候阅读本篇题解。

Solution

Part 1

首先,抛开这个线段树的 tag,题目询问连通性,点边量级相同,考虑线段树分治套上可撤销并查集做离线的动态图连通性即可。优点是直接无脑套模板就好。

考虑不用动态图连通性,从这个图的特点出发的做法。最终联通的路径一定是一个蛇形,特殊情况是在首尾两端路径形态可能不同。发现单行的话很好做,但是双行的话最终答案路径的形态可能非常复杂,指会在两行之间交替出现,非常不好维护。

Part 2

考虑这种在类似序列形态的图上“走”的常见套路。通常在一类问题中,我们会考虑用倍增来模拟或者加速我们模拟路径行走的过程。形象地,我们会把可能的行走的路径拆分成若干段,一段一段地跳。即忽略过程,只记录结果,分段解决子问题

回归到这道题,题目询问的是连通性。维护连通性,除了并查集直接维护之外,我们可以考虑从它的传递性入手。同时,发现题目每一次询问的都是在列数上的一个区间,既然是询问区间,我们可以尝试延用上面倍增的思路。

本质上是把原问题拆分成子问题,回答询问时合并拆分出来的、已经预处理过结果的子问题。优点是可以很好地平衡我们修改和查询的复杂度,前提是答案具有可拆段合并性。这种思想其实很常见,比如分块、树剖优化 dp、倍增等等,还有就是本题的线段树优化。表面上的区别是 长度相同分段、不基于序列形态的分段、以及多种长度的分段。

而这道题也能用这种思维,一是根据连通性的传递性,我们的答案具有可拆分、可合并的特性,二是询问可视为基于序列形态上的,这给我们提供了充分划分子问题解决的条件。

Part 3

言归正传。考虑我们把最后联通 (r1,c1)(r2,c2) 的路径拆成一段一段,那么每一段我们关心的只是它的起点和终点以及它们俩的连通性。同时,根据这一段路径起点终点的列数,我们 似乎 可以把这段路径放入一段列数区间考虑。具体地,对于列数区间 [l,r],我们只维护它的 左上角、左下角、右上角、右下角 相互之间的连通性。

但这样还是不够。因为我们可能会有从列 l 出发,走到列 k,然后回到列 r,但是 l<r<k 的情况。也即是说,此时我们的路径可能绕到区间 [l,r] 外面,“间接地”把它的两个端点联通起来了。这种情况如何维护?解决方法就是不维护。重新修改定义:对于区间 [l,r],考虑维护 只走它内部的边 的前提下,它四个角两两之间的连通性。

这样一来,对于上述的特殊情况,我们有两种解决方式:

Part 4

然后考虑怎么维护上面定义的 [l,r] 四个角两两之间的连通性。因为连通性具有传递性,所以这个问题显然可以再一步递归到子问题 [l,mid][mid+1,r],显然可以通过它们两各自的答案以及 连接这两段区间的边 的存在性(即是否“疏通”或“拥堵”)合并得到。通过不断这样二分递归到子问题,且合并答案的复杂度是 O(1) 的,我们就可以 O(\log n) 得到一个区间的答案了。同理,修改也是 O(\log n) 的,因为被“波及”到的区间同理也只有 O(\log n) 个。

很显然,这个结构就是线段树的结构。而这个最终的解法也是我们非常熟悉的,用线段树维护、合并带修改的信息。这样,我们就得到了 O(m\log n) 的做法。

Bonus:同理,这道题也能用分块维护。

希望经过上述两千字的分析(如果你看明白我在胡什么?),你不会再对题解区“考虑线段树”感到莫名其妙和没有头绪了。

Code

被误导了调了好久……

其实没有什么可说的,合并的地方自己画画图搞清楚细节就可以了,回答查询的时候分类讨论一下即可。

注释就不加了,用 0 表示区间左上角,1 表示区间左下角,2 表示区间右上角,3 表示区间右下角。有需要的可以参考一下代码。

````cpp #include<bits/stdc++.h> using namespace std; #define rep(i, a, b) for(int i = a; i <= b; ++i) #define per(i, a, b) for(int i = a; i >= b; --i) const int maxn = 1e5 + 5; int n; #define ls (x << 1) #define rs (x << 1 | 1) struct tree{ bool g[4][4], f[2]; tree(){ memset(g, 0, sizeof g); memset(f, 0, sizeof f);} }t[maxn << 2]; inline tree Merge(tree x, tree y){ tree z; z.f[0] = y.f[0], z.f[1] = y.f[1]; rep(i, 0, 3) z.g[i][i] = 1; bool Ov = x.f[0] & x.f[1]; z.g[0][1] = x.g[0][1] | (x.g[0][2] & Ov & y.g[0][1] & x.g[1][3]); z.g[0][2] = (x.g[0][2] & x.f[0] & y.g[0][2]) | (x.g[0][3] & x.f[1] & y.g[1][2]); z.g[0][3] = (x.g[0][2] & x.f[0] & y.g[0][3]) | (x.g[0][3] & x.f[1] & y.g[1][3]); z.g[1][2] = (x.g[1][2] & x.f[0] & y.g[0][2]) | (x.g[1][3] & x.f[1] & y.g[1][2]); z.g[1][3] = (x.g[1][2] & x.f[0] & y.g[0][3]) | (x.g[1][3] & x.f[1] & y.g[1][3]); z.g[2][3] = y.g[2][3] | (y.g[0][2] & Ov & x.g[2][3] & y.g[1][3]); return z; } inline void up(int x){ t[x] = Merge(t[ls], t[rs]); } inline void build(int x, int l, int r){ if(l == r){ rep(i, 0, 3) t[x].g[i][i] = 1; t[x].g[0][2] = t[x].g[1][3] = 1; return; } int mid = l + r >> 1; build(ls, l, mid), build(rs, mid + 1, r); up(x); } inline tree qryr(int x, int l, int r, int L, int R){ if(l >= L and r <= R) return t[x]; int mid = l + r >> 1; if(R <= mid) return qryr(ls, l, mid, L, R); if(L > mid) return qryr(rs, mid + 1, r, L, R); return Merge(qryr(ls, l, mid, L, R), qryr(rs, mid + 1, r, L, R)); } inline void updtp1(int x, int l, int r, int p, int o, bool v){ if(r == p){ t[x].f[o] = v; int mid = l + r >> 1; if(l != r){ updtp1(rs, mid + 1, r, p, o, v); up(x); } return; } int mid = l + r >> 1; if(mid == p){ t[ls].f[o] = v; updtp1(ls, l, mid, p, o, v); up(x); return; } if(p < mid) updtp1(ls, l, mid, p, o, v); else updtp1(rs, mid + 1, r, p, o, v); up(x); } inline void updtp2(int x, int l, int r, int p, bool v){ if(l == r){ t[x].g[0][1] = t[x].g[2][3] = t[x].g[0][3] = t[x].g[1][2] = v; return; } int mid = l + r >> 1; if(p <= mid) updtp2(ls, l, mid, p, v); else updtp2(rs, mid + 1, r, p, v); up(x); } int main(){ scanf("%d", &n); build(1, 1, n); while(true){ char str[15]; scanf("%s", str); if(str[0] == 'E') return 0; int r1, c1, r2, c2; scanf("%d%d%d%d", &r1, &c1, &r2, &c2); if(str[0] == 'A'){ if(c1 > c2) swap(r1, r2), swap(c1, c2); tree nw = qryr(1, 1, n, c1, c2); tree lft = qryr(1, 1, n, 1, c1); tree rgh = qryr(1, 1, n, c2, n); if(r1 == r2 and c1 == c2){ printf("Y\n"); continue;} if(c1 == c2){ if(nw.g[0][1] or lft.g[2][3] or rgh.g[0][1]) printf("Y\n"); else printf("N\n"); continue; } bool u = r1 - 1, v = r2 - 1; if(nw.g[u][2 + v]){ printf("Y\n"); continue;} bool cl = lft.g[2 + min(u, !u)][2 + max(u, !u)], cr = rgh.g[min(v, !v)][max(v, !v)]; if((cl and nw.g[!u][2 + v]) or (cr and nw.g[u][2 + (!v)]) or (cl and cr and nw.g[!u][2 + (!v)])) printf("Y\n"); else printf("N\n"); } else{ if(r1 == r2) updtp1(1, 1, n, c1, r1 - 1, str[0] == 'O' ? 1 : 0); else updtp2(1, 1, n, c1, str[0] == 'O' ? 1 : 0); } } return 0; } ````