【学习笔记/习题总结】kruskal重构树
kruskal 重构树
注:默认您学会了求最小生成树的 kruskal 算法,并且知道何为最小瓶颈生成树和最小瓶颈路。
定义:
在跑 kruskal 的过程中我们会从小到大加入若干条边,我们仍然按照这个顺序加边,并且视这条边为一个节点,点权为加入边的边权。
一次加边会合并两个集合,我们分别以这两个集合的根节点为新建点的左、右儿子,然后将两个集合和新点合并为一个集合,将新建点设为根。
在进行 \(n - 1\) 轮加边后,我们就得到了一棵恰有 \(n\) 个叶子的二叉树,同时每个非叶子节点恰有两个儿子。这棵树就是 kruskal 重构树。
举个例子,这张图:
建出 kruskal 重构树是:
点上括号里的数表示点权。
实现:
其实和 kruskal 求最小生成树的实现一样,对所有边排序,依次考虑这条边可不可以被加入,通过并查集维护两点之间的联通性,没了。
struct Union_Set{
int fa[MAXN];
void init(int n){
for(register int i = 1; i <= n; i++) fa[i] = i;
}
int Find(int x){
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
}U; //并查集
void Kruskal(){
U.init(n << 1); //初始化并查集
int tot = 0; sum = n; //tot 是加进去了多少边,sum 是重构树上点的数量
sort(line + 1, line + 1 + m, cmp);
for(register int i = 1; i <= m; i++){
int u = line[i].from, v = line[i].to;
int fa_u = U.Find(u), fa_v = U.Find(v);
if(fa_u != fa_v){
++tot;
val[++sum] = line[i].dis;
Add(sum, fa_u), Add(fa_u, sum);
Add(sum, fa_v), Add(fa_v, sum);
}
if(tot == n - 1) break; //加进 n - 1 条边一定能构成树
}
}
性质:
由于 kruskal 重构树是一棵二叉树,并且是依次加边,所以它有一些美妙的性质:
如果我们将叶子节点的权值视为 \(0\),则整棵树满足堆结构。所以任意两点间路径的最小权值的最大值即为它们 \(LCA\) 的点权。
也就是说,到点 \(u\) 的简单路径上最大边权的最小值 \(≤ val\) 的所有点 \(y\) 均在 kruskal 重构树上的某一颗子树内,且恰为这棵子树中的叶子结点。
题:
P4768 [NOI2018] 归程
\(\textit{Description}\)
\(N\) 个节点 \(M\) 条边的无向图,用 \(l\),\(a\) 表示长度,海拔。
有 \(Q\) 次询问,给定起点 \(v\),水位线 \(p\),只能经过海拔不低于 \(p\) 的边,求能到达的点中距离 \(1\) 号节点最近的距离。
\(\textit{Solution}\)
首先跑一遍单源最短路,预处理出每个点到 \(1\) 号节点的最短路径,注意到这是归程,所以 spfa
会被卡,所以要用 dijstra
。然后建出关于海拔 \(a\) 的 kruskal 重构树,要关于海拔 \(a\) 降序排序使得 kruskal 重构树满足大根堆性质,那么能到达的点是重构树中一个子树的所有叶子节点。然后我们去找节点 \(v\) 到根的路径上最浅的海拔不低于 \(p\) 的点,这一步可以倍增处理。然后以该节点为根的子树中的叶子结点到 \(1\) 节点的最短路径就是答案,直接 dfs
时预处理就行,不要像写这篇博的傻逼一样把树拍扁了再在 dfs
序上线段树处理最小值。
\(\textit{Code}\)
Code
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 2e5 + 10, MAXM = 4e5 + 10, SIZE = 25;
const int INF = 2147483647;
int t, n, m, q, k, s, cnt, sum, num, last;
int head[MAXN << 1], dis[MAXN], data[MAXN];
int val[MAXN << 1], lpos[MAXN << 1], rpos[MAXN << 1];
int fa[MAXN << 1][SIZE];
bool vis[MAXN];
struct Line{
int from, to, dis;
}line[MAXM];
inline bool cmp(const Line &a, const Line &b){
return a.dis > b.dis;
}
struct Edge{
int to, next, dis;
}e[MAXM << 1];
inline void Add(int u, int v, int w){
e[++cnt].to = v;
e[cnt].dis = w;
e[cnt].next = head[u];
head[u] = cnt;
}
struct Road{
int dis, pos;
bool operator > (const Road &a) const{
return dis > a.dis;
}
};
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
void Dijkstra(int u){
priority_queue< Road, vector<Road>, greater<Road> > q;
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));
dis[u] = 0;
q.push((Road){0, u});
while(!q.empty()){
Road t = q.top(); q.pop();
if(vis[t.pos]) continue;
vis[t.pos] = true;
for(register int i = head[t.pos]; i; i = e[i].next){
int v = e[i].to;
if(dis[v] > dis[t.pos] + e[i].dis){
dis[v] = dis[t.pos] + e[i].dis;
if(!vis[v]) q.push((Road){dis[v], v});
}
}
}
}
struct Union_Set{
int fa[MAXN << 1];
void init(int n){
for(register int i = 1; i <= n; i++) fa[i] = i;
}
int Find(int x){
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
}U;
void Kruscal(){
int tot = 0; cnt = 0;
memset(head, 0, sizeof(head));
U.init(n << 1);
sort(line + 1, line + 1 + m, cmp);
for(register int i = 1; i <= m; i++){
int u = line[i].from, v = line[i].to;
int fa_u = U.Find(u), fa_v = U.Find(v);
if(fa_u != fa_v){
++tot;
val[++sum] = line[i].dis;
Add(fa_u, sum, 0), Add(sum, fa_u, 0);
Add(fa_v, sum, 0), Add(sum, fa_v, 0);
U.fa[fa_u] = U.fa[fa_v] = sum;
}
if(tot == n - 1) break;
}
}
void dfs(int rt, int father){
lpos[rt] = num + 1;
fa[rt][0] = father;
for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
int son = 0;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
++son, dfs(v, rt);
}
if(!son) data[++num] = dis[rt];
rpos[rt] = num;
}
struct Segment_Tree{
struct Tree{
int l, r;
int min;
}tr[MAXN << 2];
inline int lson(int rt){
return rt << 1;
}
inline int rson(int rt){
return rt << 1 | 1;
}
void Pushup(int rt){
tr[rt].min = min(tr[lson(rt)].min, tr[rson(rt)].min);
}
void Build(int rt, int l, int r){
tr[rt].l = l, tr[rt].r = r;
if(l == r){
tr[rt].min = data[l];
return;
}
int mid = (l + r) >> 1;
Build(lson(rt), l, mid);
Build(rson(rt), mid + 1, r);
Pushup(rt);
}
int Query_Min(int rt, int l, int r){
if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt].min;
int ans = INF;
int mid = (tr[rt].l + tr[rt].r) >> 1;
if(l <= mid) ans = min(ans, Query_Min(lson(rt), l, r));
if(r > mid) ans = min(ans, Query_Min(rson(rt), l, r));
return ans;
}
}S;
int Query(int v, int p){
for(register int i = 24; i >= 0; i--)
if(fa[v][i] && val[fa[v][i]] > p) v = fa[v][i];
return S.Query_Min(1, lpos[v], rpos[v]);
}
void Clear(){
cnt = num = sum = last = 0;
memset(fa, 0, sizeof(fa));
memset(head, 0, sizeof(head));
}
int main(){
t = read();
while(t--){
Clear();
n = read(), m = read(), sum = n;
for(register int i = 1; i <= m; i++){
int u, v, w, p;
u = read(), v = read(), w = read(), p = read();
Add(u, v, w), Add(v, u, w);
line[i] = (Line){u, v, p};
}
Dijkstra(1);
Kruscal();
dfs(sum, 0);
S.Build(1, 1, n);
q = read(), k = read(), s = read();
for(register int i = 1; i <= q; i++){
int v, p;
v = (read() + k * last - 1) % n + 1, p = (read() + k * last) % (s + 1);
last = Query(v, p);
printf("%d\n", last);
}
}
return 0;
}
P1967 [NOIP2013] 货车运输
\(\textit{Description}\)
给定一个 \(N\) 个点 \(M\) 条边的无向图,每条边有一个权值,\(Q\) 次询问,求 \(u\),\(v\) 两点路径上最大的权值的最小值。
\(\textit{Solution}\)
权值的限制可以想到 kruskal 重构树,求最大权值的最小值显然要按照最大生成树建重构树,\(LCA(u, v)\) 的权值即为 \(u\) 到 \(v\) 路径上的最大权值的最小值。
\(\textit{Code}\)
纯口胡,没写过,写的两个 \(\log\) 的树剖。
P3280 [SCOI2013] 摩托车交易
\(\textit{Description}\)
写了就没意思了。
\(\textit{Solution}\)
先说结论:题目里两个限制都是假的。
对于要求最后要卖光:
我们只需要尽可能多的携带黄金,不用在意是否能卖光。因为实际情况下我们可以调整购入黄金的量来达到要求。
对于不能丢弃:
我们能买黄金就尽量买,在路途中丢弃黄金和购入时购入恰好的量是等效的。
一个城市有车站的话就向上一个车站连一条边权为 \(inf\) 的边,反正最后能缀在一起就行了。
然后按照最大生成树建重构树,求两点之间的 \(LCA\),用个变量记录一下当前携带的黄金数量模拟就行了。
\(\textit{Code}\)
纯口胡,没写过,写的两个 \(\log\) 的树剖。
P4197 Peaks
\(\textit{Description}\)
给定一个无向图,有 \(Q\) 个询问,求从点 \(v\) 出发,只经过权值小于等于 \(x\) 的边能到达的所有点中第 \(k\) 大的权值。
\(\textit{Solution}\)
kruskal 重构树和主席树缝起来。
按照最小生成树建出重构树,倍增找到点 \(v\) 的祖先中最浅的权值小于等于 \(x\) 的点 \(u\),能到达的所有点就是以 \(u\) 为根的子树的叶子节点。
我们可以通过树剖等知识知道,一棵子树内的所有点的 dfs
序是一个连续的区间。把重构树拍扁,注意只有叶子结点存的是原图的点权,所以每个非叶子结点只需要记录它包含的叶子节点的区间就行了,然后建主席树,查询区间第 \(k\) 大即可。
\(\textit{Code}\)
Code
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 10, MAXM = 5e5 + 10, SIZE = 25;
int n, m, q, cnt, len, lim, sum, num;
int head[MAXN << 1], hig[MAXN], data[MAXN];
int val[MAXN << 1], lpos[MAXN << 1], rpos[MAXN << 1];
int root[MAXN];
int fa[MAXN << 1][SIZE];
struct Line{
int from, to, dis;
}line[MAXM];
inline bool cmp(const Line &a, const Line &b){
return a.dis < b.dis;
}
struct Edge{
int to, next;
}e[MAXM << 1];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
struct Union_Set{
int fa[MAXN << 1];
void init(int n){
for(register int i = 1; i <= n; i++) fa[i] = i;
}
int Find(int x){
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
}U;
struct Chairman_Tree{
int tot;
struct Tree{
int lson, rson;
int size;
}tr[MAXN * 50];
#define lson(x) tr[x].lson
#define rson(x) tr[x].rson
void Build(int &rt, int l, int r){
rt = ++tot;
if(l == r) return;
int mid = (l + r) >> 1;
Build(lson(rt), l, mid);
Build(rson(rt), mid + 1, r);
}
void Update(int &rt, int last, int pos, int L, int R){
rt = ++tot;
tr[rt].lson = tr[last].lson;
tr[rt].rson = tr[last].rson;
tr[rt].size = tr[last].size + 1;
if(L == R) return;
int mid = (L + R) >> 1;
if(pos <= mid) Update(lson(rt), lson(last), pos, L, mid);
else Update(rson(rt), rson(last), pos, mid + 1, R);
}
int Query_Kth(int rt_l, int rt_r, int k, int L, int R){
if(tr[rt_r].size - tr[rt_l].size < k) return -1;
if(L == R) return L;
int mid = (L + R) >> 1;
int cnt = tr[lson(rt_r)].size - tr[lson(rt_l)].size;
if(k <= cnt) return Query_Kth(lson(rt_l), lson(rt_r), k, L, mid);
else return Query_Kth(rson(rt_l), rson(rt_r), k - cnt, mid + 1, R);
}
}C;
void Kruscal(){
int tot = 0;
U.init(n << 1);
sort(line + 1, line + 1 + m, cmp);
for(register int i = 1; i <= m; i++){
int u = line[i].from, v = line[i].to;
int fa_u = U.Find(u), fa_v = U.Find(v);
if(fa_u != fa_v){
++tot;
val[++sum] = line[i].dis;
U.fa[fa_u] = U.fa[fa_v] = sum;
Add(sum, fa_u), Add(fa_u, sum);
Add(sum, fa_v), Add(fa_v, sum);
}
if(tot == n - 1) break;
}
}
void dfs(int rt, int father){
lpos[rt] = num;
fa[rt][0] = father;
for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
int son = 0;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
++son;
dfs(v, rt);
}
if(!son){
++num;
C.Update(root[num], root[num - 1], hig[rt], 1, lim);
}
rpos[rt] = num;
}
int Query(int v, int x, int k){
for(register int i = 24; i >= 0; i--)
if(fa[v][i] && val[fa[v][i]] <= x) v = fa[v][i];
k = rpos[v] - lpos[v] - k + 1;
if(k < 1) return -1;
return C.Query_Kth(root[lpos[v]], root[rpos[v]], k, 1, lim);
}
int main(){
n = read(), m = read(), q = read(), sum = n;
for(register int i = 1; i <= n; i++)
hig[i] = read(), data[i] = hig[i];
sort(data + 1, data + 1 + n);
len = unique(data + 1, data + 1 + n) - data - 1;
for(register int i = 1; i <= n; i++){
int pos = lower_bound(data + 1, data + 1 + len, hig[i]) - data;
hig[i] = pos, lim = max(lim, pos);
}
for(register int i = 1; i <= m; i++){
int u, v, w;
u = read(), v = read(), w = read();
line[i] = (Line){u, v, w};
}
Kruscal();
C.Build(root[0], 1, lim);
dfs(sum, 0);
for(register int i = 1; i <= q; i++){
int v, x, k, ans;
v = read(), x = read(), k = read();
ans = Query(v, x, k);
if(ans != -1) ans = data[ans];
printf("%d\n", ans);
}
return 0;
}
P7834 [ONTAK2010] Peaks 加强版
只加强了一个强制在线,然而 kruskal 重构树就是在线做法。
[AGC002D] Stamp Rally
\(\textit{Description}\)
给定一张无向图,每次询问从 \(x\) 和 \(y\) 分别出发,一共要经过 \(z\) 个点(经过相同的点只算一次),使得走过编号最大的边最小。
\(\textit{Solution}\)
以边的编号为权值,按照最小生成树建重构树,每次二分需要走过的最大的编号,让 \(x\),\(y\) 分别跳到最浅的满足要求的祖先。则以该节点为根的子树中的叶子结点都是可以到达的,判断两节点包含的叶子结点的数量是否大于等于 \(z\) 即可。
\(\textit{Code}\)
Code
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 10, SIZE = 25;
int n, m, q, cnt, sum, maxn;
int head[MAXN << 1], val[MAXN << 1], siz[MAXN << 1];
int fa[MAXN << 1][SIZE];
struct Line{
int from, to, dis;
}line[MAXN];
bool cmp(const Line &a, const Line &b){
return a.dis < b.dis;
}
struct Edge{
int to, next;
}e[MAXN << 2];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
struct Union_Set{
int fa[MAXN << 1];
void init(int n){
for(register int i = 1; i <= n; i++) fa[i] = i;
}
int Find(int x){
return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
}U;
void Kruscal(){
int tot = 0;
U.init(n << 1);
sort(line + 1, line + 1 + m, cmp);
for(register int i = 1; i <= m; i++){
int u = line[i].from, v = line[i].to;
int fa_u = U.Find(u), fa_v = U.Find(v);
if(fa_u != fa_v){
++tot;
val[++sum] = line[i].dis;
maxn = max(maxn, val[sum]);
U.fa[fa_u] = U.fa[fa_v] = sum;
Add(sum, fa_u), Add(fa_u, sum);
Add(sum, fa_v), Add(fa_v, sum);
}
}
}
void dfs(int rt, int father){
fa[rt][0] = father;
for(register int i = 1; fa[rt][i - 1]; i++) fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
int son = 0;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
++son, dfs(v, rt);
siz[rt] += siz[v];
}
if(!son) ++siz[rt];
}
bool Check(int x, int y, int num, int tot){
for(register int i = 24; i >= 0; i--)
if(fa[x][i] && val[fa[x][i]] <= num) x = fa[x][i];
for(register int i = 24; i >= 0; i--)
if(fa[y][i] && val[fa[y][i]] <= num) y = fa[y][i];
if(x == y) return (siz[x] >= tot);
else return (siz[x] + siz[y] >= tot);
}
int Query(int x, int y, int z){
int l = 1, r = maxn, ans = 0;
while(l <= r){
int mid = (l + r) >> 1;
if(Check(x, y, mid, z)) ans = mid, r = mid - 1;
else l = mid + 1;
}
return ans;
}
int main(){
n = read(), m = read(), sum = n;
for(register int i = 1; i <= m; i++){
int u, v, w;
u = read(), v = read(), w = i;
line[i] = (Line){u, v, w};
}
Kruscal();
dfs(sum, 0);
q = read();
for(register int i = 1; i <= q; i++){
int x, y, z;
x = read(), y = read(), z = read();
printf("%d\n", Query(x, y, z));
}
return 0;
}
P3684 [CERC2016]机棚障碍 Hangar Hurdles
\(\textit{Description}\)
太长,不想写了。
\(\textit{Soltion}\)
对于每个位置求出它能通过的最大的矩形大小,转化成求最大瓶颈路问题。
求最大矩形大小可以二分查找。记录一个二维前缀和 \(sum_{i,j}\),遇到障碍物加 \(1\),设当前点为 \((x, y)\),二分 \(len\),使得\(sum_{x - len, y - len}\) 到 \(sum_{x + len, y + len}\) 的和为 \(0\),最后最大的矩形就是 \(len \times 2 + 1\)。
然后去连边,边权是两端点能通过的最大的矩形的最小值。从当前点分别向它的右边和下边连边即可。注意的是障碍物也需要连边,否则一些不能通达的点不会出现在重构树里,查询时会寄掉。有障碍物只需要把边权设为 \(0\) 即可。
按照最大生成树去建重构树,询问就是求两点之间的 \(LCA\),如果 \(LCA\) 的权值是 \(0\) 的话说明不能通达,判掉即可。
\(\textit{Code}\)
Code
#include<cstdio>
#include<algorithm>
#define X first
#define Y second
#define Pair pair< int, int >
#define Make(x, y) make_pair(x, y)
using namespace std;
const int MAXN = 1010, MAXM = 1e6 + 10, SIZE = 35;
int n, m, q, cnt, all, tot, teg;
int lim[MAXM], head[MAXM << 1], val[MAXM << 1];
int fa[MAXM << 1], son[MAXM << 1], deep[MAXM << 1], size[MAXM << 1];
int top[MAXM << 1], dfn[MAXM << 1];
int num[MAXN][MAXN], sum[MAXN][MAXN];
char map[MAXN][MAXN];
struct Edge{
int to, next;
}e[MAXM << 2];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
struct Line{
int from, to, dis;
}line[MAXM << 2];
bool cmp(const Line &a, const Line &b){
return a.dis > b.dis;
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
bool Check(int x, int y, int len){
int lx = x - len, ly = y - len;
int rx = x + len, ry = y + len;
return sum[rx][ry] - sum[lx - 1][ry] - sum[rx][ly - 1] + sum[lx - 1][ly - 1] == 0;
}
int Lower_Bound(int x, int y){
int l = 0, r = min(min(x - 1, n - x), min(y - 1, n - y));
int ans = 0;
while(l <= r){
int mid = (l + r) >> 1;
if(Check(x, y, mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
return ans;
}
struct Union_Set{
int fa[MAXM << 1];
void init(int n){
for(register int i = 1; i <= n; i++) fa[i] = i;
}
int Find(int x){
return x == fa[x] ? x : fa[x] = Find(fa[x]);
}
}U;
void Kruscal(){
int num = 0; all = tot;
U.init(tot << 1);
sort(line + 1, line + 1 + m, cmp);
for(register int i = 1; i <= m; i++){
int u = line[i].from, v = line[i].to, w = line[i].dis;
int fa_u = U.Find(u), fa_v = U.Find(v);
if(fa_u != fa_v){
++num;
val[++all] = w;
U.fa[fa_u] = U.fa[fa_v] = all;
Add(all, fa_u), Add(fa_u, all);
Add(all, fa_v), Add(fa_v, all);
}
if(num == tot - 1) break;
}
}
void dfs_deep(int rt, int father, int depth){
size[rt] = 1;
fa[rt] = father;
deep[rt] = depth;
int max_son = -1;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
dfs_deep(v, rt, depth + 1);
size[rt] += size[v];
if(size[v] > max_son){
son[rt] = v;
max_son = size[v];
}
}
}
void dfs_top(int rt, int top_fa){
dfn[rt] = ++teg;
top[rt] = top_fa;
if(!son[rt]) return;
dfs_top(son[rt], top_fa);
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(!dfn[v]) dfs_top(v, v);
}
}
int Get_LCA(int x, int y){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
return x;
}
int main(){
n = read();
for(register int i = 1; i <= n; i++){
scanf("%s", map[i] + 1);
for(register int j = 1; j <= n; j++){
num[i][j] = ++tot;
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1];
if(map[i][j] == '#') ++sum[i][j];
}
}
for(register int i = 1; i <= n; i++){
for(register int j = 1; j <= n; j++){
if(map[i][j] == '.'){
int len = Lower_Bound(i, j);
lim[num[i][j]] = len * 2 + 1;
}
else lim[num[i][j]] = 0;
val[num[i][j]] = lim[num[i][j]];
}
}
for(register int i = 1; i <= n; i++){
for(register int j = 1; j <= n; j++){
if(j < n) line[++m] = (Line){num[i][j], num[i][j + 1], min(lim[num[i][j]], lim[num[i][j + 1]])};
if(i < n) line[++m] = (Line){num[i][j], num[i + 1][j], min(lim[num[i][j]], lim[num[i + 1][j]])};
}
}
Kruscal();
dfs_deep(all, 0, 1);
dfs_top(all, all);
q = read();
for(register int i = 1; i <= q; i++){
Pair a, b; int x, y, anc;
a.X = read(), a.Y = read(), b.X = read(), b.Y = read();
x = num[a.X][a.Y], y = num[b.X][b.Y];
anc = Get_LCA(x, y);
printf("%d\n", val[anc]);
}
return 0;
}
还有CF上的两道题,但我找不到了,咕了。
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16889695.html