直接上一道例题:
给你一张 n n n个点, m m m条边的图, q q q组询问,每次需要求 x , y x,y x,y路径最大边的最小值.
由于我们每次选最小的边加入一定最优,所以原问题可以转化为求一棵 M S T MST MST.
直接讲如何构树:
我们把边权转点权,如果有边
(
x
,
y
,
T
)
(x,y,T)
(x,y,T)在
M
S
T
MST
MST内,我们用一个点连接对应的连通块.
原图:
重构后:
具体的,一开始有
n
n
n个叶子结点,对应着原图的结点.
当有一条边
(
x
,
y
,
T
)
(x,y,T)
(x,y,T)能加入
M
S
T
MST
MST时.
我们找到
x
,
y
x,y
x,y对应连通块的深度最小的点
u
,
v
u,v
u,v.
新建一个结点,连接
u
,
v
u,v
u,v并更新并查集.
k r u s k a l kruskal kruskal重构树有以下性质:
- 二叉树
- 类似大根堆
- 两点的最大点权为其 l c a lca lca对应的权值.
运用情形:当问题和瓶颈路有关时,如最大边最小,最小边最大的情况下的一些问题求解. 同时,你可以方便地找到合法点集,这个维护一下
d
f
s
dfs
dfs序即可.
不足:你不能找出对应的路径.如果一定要找出路径的话,可以考虑树剖或者倍增,如果在线的话考虑
L
C
T
LCT
LCT.
那么这道例题就非常模板了…
#include<bits/stdc++.h>
#define gc getchar()
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define mk make_pair
#define pi pair<int,int>
#define vi vector<int>
#define TP template<class o>
using namespace std;
const int N=3e4+10,M=2*N,mod=998244353,inf=2e9;
TP void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)) {if(c=='-') f=-1; c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
TP void qw(o x) {
if(x<0) putchar('-'),x=-x;
if(x/10) qw(x/10);
putchar(x%10+'0');
}
TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}
int n,m,q,fa[N],top[N],tot,val[N],f[N][20],dep[N];
struct rec {
int x,y,z;
bool operator<(rec b) const {
return z<b.z;
}
} e[M];
struct edge{int y,next;}a[N]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
void dfs(int x) {
for(int k=last[x],y;k;k=a[k].next) {
y=a[k].y;
f[y][0]=x;dep[y]=dep[x]+1;
for(int i=1;i<=16;i++) f[y][i]=f[f[y][i-1]][i-1];
dfs(y);
}
}
int lca(int x,int y) {
if(dep[x]<dep[y]) swap(x,y);
for(int k=dep[x]-dep[y],i=0;k;i++)
if(k>>i&1) x=f[x][i],k^=1<<i;
for(int i=16;i>=0;i--)
if(f[x][i]^f[y][i])
x=f[x][i],y=f[y][i];
return val[f[x][0]];
}
int main() {
qr(n); qr(m); qr(q);
for(int i=1;i<=m;i++) qr(e[i].x),qr(e[i].y),qr(e[i].z);
sort(e+1,e+m+1);
for(int i=1;i<=n;i++) fa[i]=top[i]=i;
tot=n;
for(int i=1;i<=m;i++) if(get(e[i].x)^get(e[i].y)) {
int u=get(e[i].x),v=get(e[i].y);
fa[u]=v; ++tot; ins(tot,top[u]),ins(tot,top[v]);
top[v]=tot; val[tot]=e[i].z;
}
dfs(tot);
while(q--) {
int x,y; qr(x); qr(y);
pr2(lca(x,y));
}
return 0;
}
练习:
BZOJ #3545. [ONTAK2010]Peaks
有 N N N座山峰,每座山峰有他的高度 h i h_i hi。有些山峰之间有双向道路相连,共 M M M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有 Q Q Q组询问,每组询问询问从点v开始只经过困难值小于等于 x x x的路径所能到达的山峰中第 k k k高的山峰,如果无解输出 − 1 。 -1。 −1。
首先建出
K
r
u
s
k
a
l
Kruskal
Kruskal重构树,我们倍增找到对应的子树后,那么就是子树内第
k
k
k大数.
这个用线段树合并可以简单处理.
#include<bits/stdc++.h>
#define gc getchar()
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define mk make_pair
#define pi pair<int,int>
#define vi vector<int>
#define TP template<class o>
using namespace std;
const int N=2e5+10,M=5e5+10,mod=998244353,inf=1e9;
TP void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)) {if(c=='-') f=-1; c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
TP void qw(o x) {
if(x<0) putchar('-'),x=-x;
if(x/10) qw(x/10);
putchar(x%10+'0');
}
TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}
int n,m,q,f[N][22],fa[N],top[N],dep[N],val[N],tot;
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
struct rec {
int x,y,z;
bool operator <(rec b) const {
return z<b.z;
}
} e[M];
struct edge{int y,next;} a[N]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}
struct node{int l,r,c;} tr[N*30]; int cnt,root[N];
#define lc tr[x].l
#define rc tr[x].r
void update(int &x,int l,int r,int pos) {
if(!x) x=++cnt;
tr[x].c++;
if(l==r) return ;
int mid=(l+r)/2;
if(pos<=mid) update(lc,l,mid,pos);
else update(rc,mid+1,r,pos);
}
int merge(int x,int y) {
if(!x||!y) return x|y;
int z=++cnt;
tr[z].l=merge(lc,tr[y].l);
tr[z].r=merge(rc,tr[y].r);
if(!tr[z].l&&!tr[z].r) tr[z].c=tr[x].c+tr[y].c;
else tr[z].c=tr[tr[z].l].c+tr[tr[z].r].c;
return z;
}
void solve(int x,int k) {
int l=0,r=inf,mid;
if(k>tr[x].c) puts("-1");
else {
while(l<r) {
mid=(l+r)/2;
if(tr[rc].c<k) k -= tr[rc].c,x=lc,r=mid;
else x=rc,l=mid+1;
}
pr2(l);
}
}
void dfs(int x) {
for(int k=last[x],y;k;k=a[k].next) {
y=a[k].y;dep[y]=dep[x]+1;
f[y][0]=x; for(int i=0;f[y][i];i++) f[y][i+1]=f[f[y][i]][i];
dfs(y);
}
}
int jump(int x,int k) {
for(int i=17;i>=0;i--)
if(val[f[x][i]]<=k)
x=f[x][i];
return x;
}
int main() {
qr(n); qr(m); qr(q); tot=n;
for(int i=1,x;i<=n;i++) fa[i]=top[i]=i,qr(x),update(root[i],0,inf,x);
for(int i=1;i<=m;i++) qr(e[i].x),qr(e[i].y),qr(e[i].z);
sort(e+1,e+m+1);
for(int i=1,x,y;i<=m;i++)
if((x=get(e[i].x))^(y=get(e[i].y))) {
val[++tot]=e[i].z;
ins(tot,top[x]); ins(tot,top[y]);
root[tot]=merge(root[top[x]],root[top[y]]);
fa[x]=y; top[y]=tot;
}
val[0]=2e9; dfs(tot);
for(int i=1,x,y,z;i<=q;i++) {
qr(x); qr(y); qr(z);
solve(root[jump(x,y)],z);
}
return 0;
}
Luogu P4768 [NOI2018]归程
有棵 n n n个点, m m m条边的连通图,每条边有 l , h l,h l,h长度,海拔两个信息.
你将乘不能经过积水的车回1号点.
某天下雨了,你从 x x x号点回家, y y y及以下海拔的边将会被水淹,形成积水.
这种情况你只能下车走回家,作为一名肥宅,你必须想方设法最小化步行路程,不然你就不能及时赶回家看番了.
首先海拔越高的边越好,那么我们可以建一棵以
h
h
h为标准的最大生成树对应的重构树.
那么,如果
x
x
x能跳到的最高点(满足
y
y
y限制)对应的整个子树到1的最小距离即为答案.
所以,先从1号点 d i j k s t r a dijkstra dijkstra一遍,然后重构树即可.
#include<bits/stdc++.h>
#define fi first
#define se second
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair<int,int>
#define pb push_back
#define IT iterator
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=4e5+10,M=N*2,size=1<<20,mod=998244353,inf=2e9;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); puts("");
}
int n,m,fa[N],top[N],f[N][22],val[N],tot;
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
struct rec {
int x,y,z;
bool operator <(rec b) const {
return z>b.z;
}
} e[M];
struct edge{int y,next,d;}a[M]; int len,last[N];
void ins(int x,int y,int z=0) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void init() {
memset(f+1,0,sizeof(f[0])*2*n);
for(int i=1;i<=n;i++) {
fa[i]=top[i]=i;
last[i]=0;
}
tot=n; len=0;
}
int d[N];
void dijkstra() {
priority_queue<pi> q;
memset(d+1,63,sizeof(int[n]));
d[1]=0; q.push(mk(0,1));
while(!q.empty()) {
int D=-q.top().fi,x=q.top().se; q.pop();
if(D^d[x]) continue;
for(int k=last[x],y,z;k;k=a[k].next) {
y=a[k].y; z=a[k].d;
if(d[y]>d[x]+a[k].d) {
d[y]=d[x]+a[k].d;
q.push(mk(-d[y],y));
}
}
}
}
void dfs(int x) {
if(last[x]) d[x]=inf;//子树内最小距离
for(int k=last[x],y;k;k=a[k].next) {
y=a[k].y;
f[y][0]=x;
for(int i=0;f[y][i];i++) f[y][i+1]=f[f[y][i]][i];
dfs(y); d[x]=min(d[x],d[y]);
}
}
void kruskal() {
sort(e+1,e+m+1); val[0]=-1;
len=0; memset(last+1,0,sizeof(int[2*n]));
for(int i=1,x,y;i<=m;i++)
if((x=get(e[i].x))^(y=get(e[i].y))) {
val[++tot]=e[i].z;
ins(tot,top[x]); ins(tot,top[y]);
fa[x]=y; top[y]=tot;
}
dfs(tot);
}
int solve(int x,int k) {
for(int i=18;i>=0;i--)
if(val[f[x][i]]>k)
x=f[x][i];
return d[x];
}
int main() {
int _;qr(_); while(_--) {
qr(n); qr(m); init();
for(int i=1,x,y,l,h;i<=m;i++) {
qr(x); qr(y); qr(l); qr(h);
e[i]=(rec){x,y,h};
ins(x,y,l); ins(y,x,l);
}
dijkstra();
kruskal();
int q,k,s,x,y,ans=0;
qr(q); qr(k); qr(s);
while(q--) {
qr(x); qr(y);
x=(x+k*ans-1)%n+1;
y=(y+k*ans)%(s+1);
ans=solve(x,y);
pr2(ans);
}
}
return 0;
}
Luogu P4899 [IOI2018] werewolf 狼人
我们以点权从大到小和从小到大依次构树,得到 A , B A,B A,B.
A往上跳即可得到人的可达位置.
B往上跳即可得到狼人的可达位置.
此时我们判断一下 A , B A,B A,B是否可交即可,这个我们对于每个B中的节点维护子树内 A A A的 d f s dfs dfs序集合即可.
#include<bits/stdc++.h>
#define gc getchar()
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
#define mk make_pair
#define fi first
#define se second
#define pi pair<int,int>
#define vi vector<int>
#define TP template<class o>
using namespace std;
const int N=2e5+10,M=8e5+10,mod=998244353,inf=2e9;
TP void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)) {if(c=='-') f=-1; c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
TP void qw(o x) {
if(x<0) putchar('-'),x=-x;
if(x/10) qw(x/10);
putchar(x%10+'0');
}
TP void pr1(o x) {qw(x); putchar(' ');}
TP void pr2(o x) {qw(x); puts("");}
int n,m,q;
vector<int> e[N];
struct Kruskal {
bool flag;
int fa[N],tot,in[N],out[N],b[N],f[N][22];
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]);}
struct edge{int y,next;}a[N*2]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}
void dfs(int x) {
in[x]=++tot; b[tot]=x;
for(int k=last[x],y;k;k=a[k].next) {
y=a[k].y; f[y][0]=x;
for(int i=0;f[y][i];i++) f[y][i+1]=f[f[y][i]][i];
dfs(y);
}
out[x]=tot;
}
void init(bool v) {//true为人的限制,以点权建立小根树
for(int i=1;i<=n;i++) fa[i]=i;
flag=v;
if(v) {
for(int i=n; i;i--)
for(int y:e[i]) {
int u=get(i),v=get(y);
if(i<y&&u^v) {
ins(u,v);
fa[v]=u;
}
}
}
else {
for(int i=1;i<=n;i++)
for(int y:e[i]) {
int u=get(i),v=get(y);
if(i>y&&u^v) {
ins(u,v);
fa[v]=u;
}
}
}
dfs(get(1));
}
pi solve(int x,int k) {
if(flag) {
if(x<k) return mk(0,0);
for(int i=18;i>=0;i--)
if(f[x][i]>=k)
x=f[x][i];
}
else {
if(x>k) return mk(0,0);
for(int i=18;i>=0;i--)
if(f[x][i]&&f[x][i]<=k)
x=f[x][i];
}
return mk(in[x],out[x]);
}
} a,b;
struct node {int l,r,c;} tr[N*40]; int tot,root[N];
#define lc tr[x].l
#define rc tr[x].r
void update(int &x,int y,int l,int r,int pos) {
tr[x=++tot]=tr[y]; tr[x].c++;
if(l==r) return ;;
int mid=(l+r)/2;
if(pos<=mid) update(lc,tr[y].l,l,mid,pos);
else update(rc,tr[y].r,mid+1,r,pos);
}
void bt() {
for(int i=1;i<=n;i++)
update(root[i],root[i-1],1,n,a.in[b.b[i]]);
}
bool ask(int x,int y,int l,int r,int L,int R) {
if(tr[x].c==tr[y].c) return 0;
if(L<=l&&r<=R) return 1;
int mid=(l+r)/2,res=0;
if(L<=mid) res = ask(lc,tr[y].l,l,mid,L,R);
if(mid< R&&!res) res = ask(rc,tr[y].r,mid+1,r,L,R);
return res;
}
int main() {
qr(n); qr(m); qr(q);
for(int i=1,x,y;i<=m;i++)
qr(x),qr(y),++x,++y,e[x].push_back(y),e[y].push_back(x);
a.init(1); b.init(0); bt();
while(q--) {
int x,y,l,r; qr(x); qr(y); qr(l); qr(r);
x++; y++; l++; r++;
pi u=a.solve(x,l),v=b.solve(y,r);
if(u.fi&&v.fi) pr2(ask(root[v.se],root[v.fi-1],1,n,u.fi,u.se));
else puts("0");
}
return 0;
}