Kruskal重构树小记
模拟赛考了,简单贺一下 oi-wiki
引入
定义
在跑
首先新建
每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,同时将两个集合的根节点分别设为新建点的左儿子和右儿子。然后我们将两个集合和新建点合并成一个集合。将新建点设为根。
不难发现,在进行
性质
-
原图中两个点之间的所有简单路径上最大边权的最小值
最小生成树上两个点之间的简单路径上的最大值 重构树上两点之间的 的权值。 -
到点
的简单路径上最大边权的最小值 的所有点 均在 重构树上的某一棵子树内,且恰好为该子树的所有叶子节点。该子树的根节点就是 到根的路径上权值 的最浅的节点
一些习题
P4768 [NOI2018] 归程
我们首先需要知道每个点
考虑要在哪里下车,显然就是子树内与
code
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int N=200010,M=400010;
int T,n,m,q,k,mx;
struct node{int x,y,z,h;}e[M];
bool v[N];
namespace GR
{
const int MM=2*M;
int head[N],ver[MM],nxt[MM],edge[MM],tot;
int d[N]; bool v[N];
void init()
{
tot=0;
memset(head,0,sizeof(head));
}
void add(int x,int y,int z)
{
ver[++tot]=y; edge[tot]=z;
nxt[tot]=head[x]; head[x]=tot;
}
void dijkstra(int s)
{
priority_queue <pii> q;
memset(v,0,sizeof(v));
memset(d,0x3f,sizeof(d));
d[s]=0; q.push({0,s});
while(q.size())
{
int x=q.top().second; q.pop();
if(v[x])
continue;
v[x]=1;
for(int i=head[x]; i; i=nxt[i])
{
int y=ver[i],z=edge[i];
if(d[x]+z<d[y])
d[y]=d[x]+z,q.push({-d[y],y});
}
}
}
}
namespace TR
{
const int NN=2*N;
int fa[NN],cnt,dep[NN],f[NN][25],val[NN],sub[NN];
vector <int> g[NN];
void init()
{
memset(dep,0,sizeof(dep));
memset(f,0,sizeof(f));
memset(val,0,sizeof(val));
memset(sub,0x3f,sizeof(sub));
for(int i=1; i<=2*n; i++)
g[i].clear();
}
void add(int x,int y)
{
g[x].push_back(y);
g[y].push_back(x);
}
bool cmp(node a,node b)
{
return a.h>b.h;
}
int get(int x)
{
if(x==fa[x])
return x;
return fa[x]=get(fa[x]);
}
void kruskal()
{
sort(e+1,e+1+m,cmp);
for(int i=1; i<=2*n; i++)
fa[i]=i;
cnt=n; int t=0;
for(int i=1; i<=m; i++)
{
int fx=get(e[i].x),fy=get(e[i].y);
if(fx==fy)
continue;
fa[fx]=fa[fy]=++cnt;
add(cnt,fx); add(cnt,fy);
val[cnt]=e[i].h;
t++;
if(t==n-1)
break;
}
}
void dfs(int x,int fa)
{
if(x<=n)
sub[x]=GR::d[x];
for(auto y:g[x])
{
if(y==fa)
continue;
dep[y]=dep[x]+1;
f[y][0]=x;
for(int i=1; i<=20; i++)
f[y][i]=f[f[y][i-1]][i-1];
dfs(y,x);
sub[x]=min(sub[x],sub[y]);
}
}
int find(int x,int p)
{
for(int i=20; i>=0; i--)
if(val[f[x][i]]>p)
x=f[x][i];
return x;
}
}
int main()
{
using namespace GR;
using namespace TR;
scanf("%d",&T);
while(T--)
{
GR::init(); TR::init();
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].z,&e[i].h);
GR::add(e[i].x,e[i].y,e[i].z); GR::add(e[i].y,e[i].x,e[i].z);
}
dijkstra(1);
kruskal();
dfs(2*n-1,0);
int last=0;
scanf("%d%d%d",&q,&k,&mx);
for(int i=1; i<=q; i++)
{
int v,p;
scanf("%d%d",&v,&p);
v=(v+k*last-1)%n+1;
p=(p+k*last)%(mx+1);
int tur=find(v,p);
last=sub[tur];
printf("%d\n",last);
}
}
return 0;
}
P7834 [ONTAK2010] Peaks 加强版
重构树上跑主席树。注意到重构树上叶子节点才有贡献,所有叶子节点构成一个区间,所以可以维护一个非叶子节点所覆盖的区间,方便操作
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100010,M=500010;
int n,m,q,a[N],rk[N],b[N];
struct node{int x,y,z;}e[M];
namespace TR
{
int cnt,fa[2*N],val[2*N],f[2*N][20];
int L[2*N],R[2*N],id[N],top,rt[2*N];
bool vis[2*N];
vector <int> g[2*N];
void add(int x,int y)
{
g[x].push_back(y);
}
bool cmp(node a,node b)
{
return a.z<b.z;
}
int get(int x)
{
if(x==fa[x])
return x;
return fa[x]=get(fa[x]);
}
void kruskal()
{
for(int i=1; i<=2*n; i++)
fa[i]=i;
cnt=n; val[0]=1e16;
for(int i=1; i<=m; i++)
{
int fx=get(e[i].x),fy=get(e[i].y);
if(fx==fy)
continue;
fa[fx]=fa[fy]=++cnt;
add(cnt,fx); add(cnt,fy);
val[cnt]=e[i].z;
}
}
void dfs(int x,int fa)
{
L[x]=top; vis[x]=1;
if(g[x].size()==0)
id[++top]=x;
for(auto y:g[x])
{
if(y==fa)
continue;
f[y][0]=x;
for(int i=1; i<=18; i++)
f[y][i]=f[f[y][i-1]][i-1];
dfs(y,x);
}
R[x]=top;
}
int find(int x,int p)
{
for(int i=18; i>=0; i--)
if(val[f[x][i]]<=p)
x=f[x][i];
return x;
}
}
namespace SegmentTree
{
int tot=0;
struct Seg
{
int lc,rc,sum;
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
#define sum(x) tree[x].sum
}tree[40*N];
void pushup(int p)
{
sum(p)=sum(lc(p))+sum(rc(p));
}
void build(int &p,int l,int r)
{
p=++tot;
if(l==r)
{
sum(p)=0;
return;
}
int mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
pushup(p);
}
void change(int &p,int pre,int l,int r,int x,int v)
{
p=++tot;
tree[p]=tree[pre];
if(l==r)
{
sum(p)+=v;
return;
}
int mid=(l+r)>>1;
if(x<=mid)
change(lc(p),lc(pre),l,mid,x,v);
else
change(rc(p),rc(pre),mid+1,r,x,v);
pushup(p);
}
int ask(int p,int pre,int l,int r,int k)
{
if(sum(p)-sum(pre)<k)
return -1;
if(l==r)
return l;
int mid=(l+r)>>1;
int rcnt=sum(rc(p))-sum(rc(pre));
if(rcnt>=k)
return ask(rc(p),rc(pre),mid+1,r,k);
return ask(lc(p),lc(pre),l,mid,k-rcnt);
}
}
signed main()
{
using namespace TR;
using namespace SegmentTree;
scanf("%lld%lld%lld",&n,&m,&q);
for(int i=1; i<=n; i++)
scanf("%lld",&a[i]),rk[i]=a[i];
for(int i=1; i<=m; i++)
scanf("%lld%lld%lld",&e[i].x,&e[i].y,&e[i].z);
sort(rk+1,rk+1+n);
int nn=unique(rk+1,rk+1+n)-(rk+1);
for(int i=1; i<=n; i++)
{
int x=lower_bound(rk+1,rk+1+nn,a[i])-rk;
b[x]=a[i]; a[i]=x;
}
sort(e+1,e+1+m,cmp);
kruskal();
for(int i=1; i<=cnt; i++)
if(!vis[i])
dfs(get(i),0);
build(rt[0],1,n);
for(int i=1; i<=n; i++)
change(rt[i],rt[i-1],1,n,a[id[i]],1);
int last=0;
while(q--)
{
int v,x,k;
scanf("%lld%lld%lld",&v,&x,&k);
v=(v^last)%n+1; k=(k^last)%n+1; x^=last;
int tur=find(v,x);
int res=ask(rt[R[tur]],rt[L[tur]],1,n,k);
if(res==-1)
puts("-1"),last=0;
else
printf("%lld\n",last=b[res]);
}
return 0;
}
CF1706E Qpwoeirut and Vertices
把编号作为边权,即求
CF1416D Graph and Queries
可以反向考虑加边,正向考虑亦可。离线询问,把边的删除时间作为边权建立小根堆,查询时树上倍增找到
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?