kruskal重构树学习笔记
kruskal重构树,其实本质上就是个可持久化的kruskal。
kruskal算法是用来求解最小生成树的,而最小生成树有另外一个性质:它也是最小瓶颈树,即图上两点之间经过边权最大的边最小的路径,都是生成树上两点间的路径。我们利用这一性质,可以在kruskal算法的求解过程中处理一些东西,例如维护图上只保留边权小于某个值的边时,图的连通性。
但是,如果这一维护过程要求强制在线,那么就必须用到kruskal重构树了。
kruskal重构树是一棵有根二叉树,其实就是把kruskal的过程用一棵二叉树记录下来。每次用一条边合并两个连通点集时,新建一个节点,点权为加入边的边权,它的两个儿子分别为表示两个连通点集的节点。
根据kruskal算法,我们可以得到kruskal重构树的一些重要性质:
1. 如果忽略叶结点,kruskal重构树满足堆性质。
2. kruskal的每个子树是原图上保留边权不大于根节点权值的边后的极大连通子图。
根据kruskal算法我们可以很轻松的证明这些性质。
例题:
这道题需要在线维护无向图上从某个起点出发,经过边权不小于某个权值的边到达的点权最小的点。显然,从该点开始经过边权不超过给定权值的边所能到达的所有点,就是kruskal重构树上该起点的,点权不小于该权值的最远祖先的子树。所以我们把kruskal重构树建出来,然后对于每个节点倍增找祖先,然后直接查询子树内的最小点权即可。
因为实在找不到哪里爆int,所以就只能暴力#define int long long...
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<ctime> #include<algorithm> #include<queue> #define ll long long #define int long long #define inf 1ll<<60 #define maxn 200010 inline ll read() { ll x=0; char c=getchar(),f=1; for(;c<'0'||'9'<c;c=getchar())if(c=='-')f=-1; for(;'0'<=c&&c<='9';c=getchar())x=x*10+c-'0'; return x*f; } inline void write(ll x) { static char buf[20],len; len=0; if(x<0)putchar('-'),x=-x; for(;x;x/=10)buf[len++]=x%10+'0'; if(!len)putchar('0'); else while(len)putchar(buf[--len]); } inline void writesp(ll x){write(x); putchar(' ');} inline void writeln(ll x){write(x); putchar('\n');} struct Edge{ int x,y,h,d; }E[2*maxn]; struct edge{ int to,nxt,d; }e[4*maxn]; struct Data{ int id,x; friend bool operator < (Data a,Data b){return a.x>b.x;} }; std::priority_queue<Data>q; int fir[2*maxn],top[2*maxn],val[2*maxn],fa[2*maxn][20]; int mark[maxn],dist[2*maxn]; int n,m,Q,K,S,cnt,tot; bool cmp(Edge a,Edge b){return a.h>b.h;} int find(int x){return x==top[x]?x:top[x]=find(top[x]);} void add_edge(int x,int y,int d){e[tot].to=y; e[tot].d=d; e[tot].nxt=fir[x]; fir[x]=tot++;} void dijkstra(int S) { for(int i=1;i<=n;i++){ mark[i]=0; dist[i]=inf; } dist[S]=0; Data init={S,0}; q.push(init); while(!q.empty()){ Data now=q.top(); q.pop(); if(mark[now.id])continue; mark[now.id]=1; for(int i=fir[now.id];~i;i=e[i].nxt) if(dist[now.id]+e[i].d<dist[e[i].to]){ dist[e[i].to]=dist[now.id]+e[i].d; Data tmp={e[i].to,dist[e[i].to]}; q.push(tmp); } } } void dfs(int now) { for(int i=1;i<=18;i++) fa[now][i]=fa[fa[now][i-1]][i-1]; if(now>n)dist[now]=inf; for(int i=fir[now];~i;i=e[i].nxt){ fa[e[i].to][0]=now; dfs(e[i].to); dist[now]=std::min(dist[now],dist[e[i].to]); } } void work() { n=read(); m=read(); memset(fir,255,sizeof(fir)); tot=0; for(int i=1;i<=m;i++){ E[i].x=read(); E[i].y=read(); E[i].d=read(); E[i].h=read(); add_edge(E[i].x,E[i].y,E[i].d); add_edge(E[i].y,E[i].x,E[i].d); } dijkstra(1); std::sort(E+1,E+m+1,cmp); for(int i=1;i<=2*n;i++)top[i]=i; memset(fir,255,sizeof(fir)); tot=0; cnt=n; for(int i=1;i<=m;i++){ int fx=find(E[i].x),fy=find(E[i].y); if(fx!=fy){ val[++cnt]=E[i].h; add_edge(cnt,fx,0); add_edge(cnt,fy,0); top[fx]=top[fy]=cnt; } } fa[cnt][0]=0; dfs(cnt); Q=read(); K=read(); S=read(); int lastans=0; while(Q--){ int v=read(),p=read(); v=(v+K*lastans-1)%n+1; p=(p+K*lastans)%(S+1); // printf("%d %d *************\n",v,p); int now=v; for(int i=18;i>=0;i--) if(fa[now][i]&&val[fa[now][i]]>p)now=fa[now][i]; lastans=dist[now]; writeln(lastans); } // for(int i=1;i<=cnt;i++) // printf("%d %d %d %d\n",i,dist[i],fa[i][0],val[i]); } signed main() { int T=read(); while(T--)work(); return 0; }
这道题有两个偏序条件,我们先按照偏序条件定义边权,然后每个询问显然就是寻找有没有一个点,能同时被起点和终点经过满足各自偏序条件的边同时到达。于是依旧上kruskal重构树,不过因为这里有边权有两个偏序条件,因此需要两维,然后就变成了一个矩形数点的问题,可以用扫描线+线段树解决。
代码:
#include "werewolf.h" #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #define maxn 200010 struct edge{ int from,to,nxt,val; }e[2*maxn]; struct Graph{ edge e[2*maxn]; int fir[2*maxn]; int cnt,tot; inline void reset(int n){memset(fir,255,sizeof(fir)); cnt=n; tot=0;} inline void add_edge(int x,int y){e[tot].from=x; e[tot].to=y; e[tot].nxt=fir[x]; fir[x]=tot++;} }T1,T2; bool cmp1(edge a,edge b){return a.val>b.val;} bool cmp2(edge a,edge b){return a.val<b.val;} int Fa[2*maxn][20]; int fa[2*maxn],val[2*maxn],id[2*maxn],size[2*maxn]; int tot; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} void dfs1(int now) { for(int i=1;i<=18;i++) Fa[now][i]=Fa[Fa[now][i-1]][i-1]; id[now]=++tot; size[now]=1; for(int i=T1.fir[now];~i;i=T1.e[i].nxt){ Fa[T1.e[i].to][0]=now; dfs1(T1.e[i].to); size[now]+=size[T1.e[i].to]; } } void dfs2(int now) { // printf("%d %d\n",now,tot+1); for(int i=1;i<=18;i++) Fa[now][i]=Fa[Fa[now][i-1]][i-1]; id[now]=++tot; size[now]=1; for(int i=T2.fir[now];~i;i=T2.e[i].nxt){ Fa[T2.e[i].to][0]=now; dfs2(T2.e[i].to); size[now]+=size[T2.e[i].to]; } } struct point{ int x,y; }a[maxn]; struct rect{ int u,d,l,r,id; }b[maxn]; bool cmp3(point a,point b){return a.x<b.x;} bool cmp4(rect a,rect b){return a.d<b.d;} int sgt[8*maxn]; void modify(int now,int l,int r,int x,int k) { // printf("%d %d %d %d %d @@@\n",now,l,r,x,k); if(l==r)sgt[now]=k; else{ int mid=(l+r)>>1; if(x<=mid)modify(now<<1,l,mid,x,k); else modify(now<<1|1,mid+1,r,x,k); sgt[now]=std::max(sgt[now<<1],sgt[now<<1|1]); } } int query(int now,int l,int r,int x,int y) { // printf("%d %d %d %d %d &&&\n",now,l,r,x,y); if(x<=l&&r<=y)return sgt[now]; else{ int mid=(l+r)>>1,ans=0; if(x<=mid)ans=std::max(ans,query(now<<1,l,mid,x,y)); if(mid<y)ans=std::max(ans,query(now<<1|1,mid+1,r,x,y)); return ans; } } int n,m,q; std::vector<int> check_validity(int N,std::vector<int> X,std::vector<int> Y,std::vector<int> S,std::vector<int> E,std::vector<int> L,std::vector<int> R) { n=N; m=X.size(); q=S.size(); for(int i=0;i<m;i++){ e[i].from=X[i]+1; e[i].to=Y[i]+1; e[i].val=std::min(X[i],Y[i]); } std::sort(e,e+m,cmp1); // for(int i=0;i<m;i++) // printf("%d %d %d\n",e[i].from,e[i].to,e[i].val); for(int i=1;i<=2*n-1;i++)fa[i]=i; T1.reset(n); for(int i=0;i<m;i++){ int fx=find(e[i].from),fy=find(e[i].to); if(fx!=fy){ val[++T1.cnt]=e[i].val; // printf("%d %d &&&\n",T1.cnt,val[T1.cnt]); // printf("%d -> %d\n%d -> %d\n",T1.cnt,fx,T1.cnt,fy); T1.add_edge(T1.cnt,fx); T1.add_edge(T1.cnt,fy); fa[fx]=fa[fy]=T1.cnt; } } tot=0; Fa[T1.cnt][0]=0; dfs1(T1.cnt); for(int i=1;i<=n;i++) a[i].x=id[i]; for(int i=0;i<q;i++){ int now=S[i]+1; for(int j=18;j>=0;j--) if(Fa[now][j]&&val[Fa[now][j]]>=L[i])now=Fa[now][j]; b[i].u=id[now]; b[i].d=id[now]+size[now]-1; } for(int i=0;i<m;i++){ e[i].from=X[i]+1; e[i].to=Y[i]+1; e[i].val=std::max(X[i],Y[i]); } std::sort(e,e+m,cmp2); // for(int i=0;i<m;i++) // printf("%d %d %d\n",e[i].from,e[i].to,e[i].val); for(int i=1;i<=2*n-1;i++)fa[i]=i; T2.reset(n); for(int i=0;i<m;i++){ int fx=find(e[i].from),fy=find(e[i].to); if(fx!=fy){ val[++T2.cnt]=e[i].val; // printf("%d %d &&&\n",T2.cnt,val[T2.cnt]); // printf("%d -> %d\n%d -> %d\n",T2.cnt,fx,T2.cnt,fy); T2.add_edge(T2.cnt,fx); T2.add_edge(T2.cnt,fy); fa[fx]=fa[fy]=T2.cnt; } } tot=0; Fa[T2.cnt][0]=0; // puts("cxktxdy"); dfs2(T2.cnt); for(int i=1;i<=n;i++) a[i].y=id[i]; for(int i=0;i<q;i++){ int now=E[i]+1; for(int j=18;j>=0;j--) if(Fa[now][j]&&val[Fa[now][j]]<=R[i])now=Fa[now][j]; b[i].l=id[now]; b[i].r=id[now]+size[now]-1; } for(int i=0;i<q;i++)b[i].id=i; // for(int i=1;i<=n;i++) // printf("%d %d %d ***\n",i,a[i].x,a[i].y); // for(int i=0;i<q;i++) // printf("%d %d %d %d %d ***\n",i,b[i].u,b[i].d,b[i].l,b[i].r); // puts("cxktxdy"); std::sort(a+1,a+n+1,cmp3); std::sort(b,b+q,cmp4); std::vector<int> ans; ans.resize(q); int last=1; for(int i=0;i<q;i++){ // printf("%d ************************\n",i); while(last<=n&&a[last].x<=b[i].d){ modify(1,1,2*n-1,a[last].y,a[last].x); ++last; } // puts("cxktxdy"); ans[b[i].id]=(query(1,1,2*n-1,b[i].l,b[i].r)>=b[i].u); // puts("cxktql"); } // puts("QAQ"); return ans; }