Kruskal 重构树
\(\text{Kruskal}\) 重构树基于 \(\text{Kruskal}\) 最小生成树算法,通过将边权化为点权实现一些奇妙东西。
构造
与 \(\text{Kruskal}\) 最小生成树算法类似。
当连边 \((u,v,w)\) 时:
-
新建节点 \(x\),将其点权设为 \(w\).
-
设 \(u,v\) 所属集合为 \(S_u,S_v\),连边 \((x,S_u)\) 和 \((x,S_v)\)。(无边权的有向边)
-
\(u,v\) 所属集合变为 \(x\).
重构树的根为最后一个点 \(2n-1\),否则会破坏重构树的形态,也才能遍历整棵树。
边权需要排序,复杂度和 \(\text{Kruskal}\) 一样都是 \(\text{O}(n\log n)\).
性质
-
重构树中的叶子节点为原树中的节点,其余都代表一条边。
-
原树中路径 \(u\rightarrow v\) 上的边权最大值为重构树上 \(lca_{u,v}\) 的点权。(假设按照最小生成树的方式加入)
因为边权被排序,显然这颗树是一个大根堆 。最大生成树则相反。
先看一下这个「BZOJ 3732」Network:
一个无向图,多次询问两点之间所有路径中最长边的最小值。
一个性质:最长边最小的路径在图的最小生成树上。
建出重构树,那么答案就是 \(lca_{u,v}\) 的点权。
一个无向连通图,一条边有长度 \(l\) 和海拔 \(a\)。
从点 \(v\) 出发,可以先经过一些海拔 \(>p\) 的边,代价为 \(0\),然后再回到 \(1\) 号点,代价为经过的边的边权,试最小化代价。
多组询问和测试数据,强制在线。
\(T\le 3\),\(n\le2\times 10^5\),\(m,Q\le4\times 10^5\),\(l\le 10^4\),\(a\le 10^9\).
\(\text{TL}=4\text{s}\).
将 \(1\) 至 \(v\) 的路径可以分为两个部分,那么这个断点 \(u\) 满足 \(u\rightarrow v\) 的边都大于 \(p\),且 \(1\rightarrow u\) 的最短路最小。
若 \(v\) 出发能到达点 \(u\),一定存在路径在原图的最大生成树上。
建出的重构树是一个小根堆,求出包含 \(v\) 的子树中根节点深度最小且点权大于 \(p\) 的子树 \(x\),那么 \(v\) 就可以遍历 \(x\) 的子树中的所有点。
深度最小的节点可以倍增求。
子树内的所有点都可能成为断点,即求每个点到 \(1\) 的最短路,然后对子树取 \(\min\) 即可。
假设 \(n,m,Q\) 同阶,时间复杂度 \(\text{O}(Tn\log n)\) .
现在是 \(20:31\),看下 \(1.5\text{h}\) 能不能码完。
什么傻逼细节只能明天调了。
我测 \(\text{tot}\) 没清零!
#include<bits/stdc++.h>
#define N 200010
#define M 400010
#define L 22
#define inf 1<<30
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int T,n,m,Q,K,S;
int head[N],ver[M<<1],nxt[M<<1],dist[M<<1],tot;
int dis[N];bool vis[N];
void add(int u,int v,int w){
nxt[++tot]=head[u];
ver[tot]=v,dist[tot]=w;
head[u]=tot;
}
priority_queue< pair<int,int> >q;
void dijkstra(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
q.push(make_pair(0,1)),dis[1]=0;
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u])continue;
vis[u]=true;
for(int i=head[u],v;i;i=nxt[i])
if(dis[v=ver[i]]>dis[u]+dist[i]){
dis[v]=dis[u]+dist[i];
q.push(make_pair(-dis[v],v));
}
}
}
struct E{
int u,v,l,a;
bool operator<(const E &x)const{
return a>x.a;
}
}e[M];
int f[N<<1],val[N<<1],node;
int fa[N<<1][L],Minn[N<<1];
vector<int>s[N<<1];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void dfs(int x){
Minn[x]=(x<=n)?dis[x]:inf;
for(int i=1;i<L;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=0,y;i<s[x].size();i++){
y=s[x][i],fa[y][0]=x,dfs(y);
Minn[x]=min(Minn[x],Minn[y]);
}
}
int main(){
T=read();
while(T--){
memset(head,0,sizeof(head));
memset(val,0,sizeof(val));
memset(fa,0,sizeof(fa));
n=read(),m=read(),node=n,tot=0;
for(int i=1,u,v,l,a;i<=m;i++){
u=read(),v=read(),l=read(),a=read();
e[i]=(E){u,v,l,a};
add(u,v,l),add(v,u,l);
}
dijkstra(),sort(e+1,e+1+m);
for(int i=1;i<n<<1;i++)f[i]=i;
for(int i=1,u,v,a,fu,fv;i<=m;i++){
u=e[i].u,v=e[i].v,a=e[i].a;
fu=find(u),fv=find(v);
if(fu!=fv){
s[++node].push_back(fu);
s[node].push_back(fv);
val[node]=a,f[fu]=f[fv]=node;
}
}
dfs(node);
Q=read(),K=read(),S=read();
for(int v,p,x,lst=0;Q;Q--){
v=(read()+K*lst-1)%n+1,p=(read()+K*lst)%(S+1),x=v;
for(int i=L-1;i>=0;i--){
if(fa[x][i]&&val[fa[x][i]]>p)x=fa[x][i];
}
printf("%d\n",lst=Minn[x]);
}
for(int i=1;i<n<<1;i++)s[i].clear();
}
return 0;
}
无向图,带点、边权,问从 \(u\) 出发,只经过权值 \(\le x\) 的边能到达的权值第 \(k\) 大的点的权值,不存在输出 \(-1\).
本题强制在线。
\(1\le n\le 10^5\),\(0\le m,q\le 5\times10^5\),权值值域 \(10^9\).
按最小生成树的方式建出重构树可以得到一个森林。
和上一题同理,可以倍增找到深度最小的 \(\le x\) 的边。
任务就是查询子树第 \(k\) 大,这个东西把树搬到 \(\text{dfs}\) 序上用主席树做即可。
#include<bits/stdc++.h>
#define N 100010
#define M 500010
#define L 20
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,q,len,a[N],tp[N];
struct E{
int u,v,w;
bool operator<(const E &x)const{
return w<x.w;
}
}e[M];
int f[N<<1],val[N<<1],node;
int fa[N<<1][L],st[N<<1],ed[N<<1],cnt;
vector<int>s[N<<1];
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int u,int v,int w){
int fu=find(u),fv=find(v);
if(fu==fv)return;
s[++node].push_back(fu);
s[node].push_back(fv);
val[node]=w,f[fu]=f[fv]=node;
}
int rt[N<<2],tot;
struct Tree{
int ls,rs,cnt;
#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define cnt(x) t[x].cnt
}t[N<<7];
int build(int l,int r){
int p=++tot;
if(l==r)return p;
int mid=(l+r)>>1;
ls(p)=build(l,mid);
rs(p)=build(mid+1,r);
return p;
}
int modify(int pre,int l,int r,int x){
int p=++tot;
t[p]=t[pre],cnt(p)++;
if(l==r)return p;
int mid=(l+r)>>1;
if(x<=mid)ls(p)=modify(ls(pre),l,mid,x);
else rs(p)=modify(rs(pre),mid+1,r,x);
return p;
}
int query(int p,int q,int l,int r,int k){
if(cnt(p)-cnt(q)<k)return 0;
if(l==r)return l;
int mid=(l+r)>>1,x=cnt(rs(p))-cnt(rs(q));
if(k<=x)return query(rs(p),rs(q),mid+1,r,k);
else return query(ls(p),ls(q),l,mid,k-x);
}
void lsh(){
sort(tp+1,tp+1+n),len=unique(tp+1,tp+1+n)-tp-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(tp+1,tp+1+len,a[i])-tp;
}
void dfs(int x){
st[x]=++cnt,rt[cnt]=(x<=n)?modify(rt[cnt-1],1,len,a[x]):rt[cnt-1];
for(int i=1;i<L;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=0,y;i<s[x].size();i++)
y=s[x][i],fa[y][0]=x,dfs(y);
ed[x]=++cnt,rt[cnt]=rt[cnt-1];
}
int main(){
n=read(),m=read(),q=read(),node=n;
for(int i=1;i<=n;i++)
a[i]=tp[i]=read();
for(int i=1,u,v,w;i<=m;i++){
u=read(),v=read(),w=read();
e[i]=(E){u,v,w};
}
sort(e+1,e+1+m);
for(int i=1;i<=n<<1;i++)f[i]=i;
for(int i=1,u,v,w;i<=m;i++){
u=e[i].u,v=e[i].v,w=e[i].w;
merge(u,v,w);
}
lsh(),rt[0]=build(1,len);
for(int i=1;i<=node;i++)
if(find(i)==i)dfs(i);
for(int u,x,k,lst=0,ans;q;q--){
u=(read()^lst)%n+1,x=(read()^lst),k=(read()^lst)%n+1;
for(int i=L-1;i>=0;i--)
if(fa[u][i]&&val[fa[u][i]]<=x)u=fa[u][i];
ans=query(rt[ed[u]],rt[st[u]-1],1,len,k);
if(!ans)lst=0,printf("-1\n");
else printf("%d\n",lst=tp[ans]);
}
return 0;
}
连森林突然爆炸了很奇怪。可能是我 \(\text{inf}\) 开小了。
顺带过了弱化还是不错的。
一颗 \(n\) 个点的树,统计以下点对 \((u,v)\) 的个数:
- \(u<v\),且恰好满足以下条件之一:
\(1.\) \(u\) 是 \(u\rightarrow v\) 上编号最小的点
\(2.\) \(v\) 是 \(u\rightarrow v\) 上编号最大的点
一共 \(m\) 次修改,每次新增点 \(n+i\),其父亲为 \(x\).
输出所有的 \(m+1\) 个答案。
全面贺题机房摆子哥!
分别定义:
\(\text{A}\) 为满足第一种的路径数。
\(\text{B}\) 为满足第二种的路径数。
\(\text{C}\) 为同时满足两种的路径数。
答案即 \(\text{A}+\text{B}-2\text{C}\).
建出大根和小根的重构树。那么
\(\text{A},\text{B}\) 分别是两颗重构树子树大小总和。
\(\text{C}\):在大根子树中找子节点有多少个是小根树上自己的祖先。
也就是计算一个点到根路径上有多少点在小根树中自己的子树里。
\(\text{dfs}\) 序 \(+\) 树状数组可以解决。
考虑解决动态挂叶子:
\(\text{A}',\text{C'}\):小根树的祖先数,记录一个 \(\text{dep}\).
\(\text{B'}\):显然这部分的贡献是 \(n+i-1\).
那么 \(\Delta_{ans}=(n+i-1)-\text{dep}(n+i)\).
人赢牛的。时间复杂度 \(O(n\log n)\).
\(\text{merge}\) 也一起抄了。
#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define lb(x) x&-x
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
ll ans;
int n,m,f[N];
vector<int>E[N],mx[N],mn[N];
int siz[N],st[N],ed[N],dep[N<<1],cnt;
void rst(){
for(int i=1;i<=n;i++)f[i]=i;
}
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int u,int v){
u=find(u),v=find(v);
if(u==v)return;
f[v]=f[u];
}
void dfs1(int u){
siz[u]=1,st[u]=++cnt;
for(int v:mx[u])
dfs1(v),siz[u]+=siz[v];
ans+=siz[u]-1,ed[u]=cnt;
}
void dfs2(int u){
siz[u]=1;
for(int v:mn[u]){
dep[v]=dep[u]+1;
dfs2(v),siz[u]+=siz[v];
}
ans+=siz[u]-1;
}
int c[N];
void add(int x,int k){
for(;x<=n;x+=lb(x))c[x]+=k;
}
int qry(int x){
int ret=0;
for(;x;x-=lb(x))ret+=c[x];
return ret;
}
void dfs3(int u){
ans-=2*(qry(ed[u])-qry(st[u]-1));
add(st[u],1);
for(int v:mn[u])dfs3(v);
add(st[u],-1);
}
int main(){
n=read(),rst();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
E[u].push_back(v),E[v].push_back(u);
}
for(int u=1;u<=n;u++)
for(int v:E[u]){
if(v<u&&find(u)!=find(v))
mx[u].push_back(find(v)),merge(u,v);
}
dfs1(n),rst();
for(int u=n;u;u--)
for(int v:E[u]){
if(v>u&&find(u)!=find(v))
mn[u].push_back(find(v)),merge(u,v);
}
dfs2(1),dfs3(1);
printf("%lld\n",ans);
m=read();
for(int i=1,x;i<=m;i++){
x=read(),dep[n+i]=dep[x]+1;
ans+=(n+i-1)-dep[n+i];
printf("%lld\n",ans);
}
return 0;
}
一个连通无向图,每次从 \(S_i\) 到 \(E_i\),问能否先走不小于 \(L_i\) 的点,再走不超过 \(R_i\) 的点。两段途中需要经过 \(\lbrack L_i,R_i\rbrack\) 中的点。
输出 \(1\) 或 \(0\).
保证输入本身合法。
\(2\le n\le 2\times10^5\),\(m\le 4\times10^5\),\(q\le 2\times 10^5\)。
同样比较显然,建两颗重构树,倍增可以爬出来两颗子树。
现在要判断子树是否有交。把它们变成 \(\text{dfs}\) 序:
两个排列 \(A,B\),询问 \(A\) 在 \(\lbrack l_1,r_1\rbrack\) 且 \(B\) 在 \(\lbrack l_2,r_2\rbrack\) 中是否有公共元素。
考虑将 \(A\) 映射入 \(B\):记 \(pos\lbrack i\rbrack\) 为 \(i\) 在 \(A\) 中的出现位置,对 \(pos\lbrack b\lbrack i\rbrack\rbrack\) 建一颗主席树,问 \(\lbrack l_2,r_2\rbrack\) 的版本内有没有 \(\lbrack l_1,r_1\rbrack\) 的元素。
\(\text{dfn}\) 撵爆炸了阳历都过不去。只能回家写了。
原来搞错了要把 \(\text{st}\) 的反函数塞进去。
#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define Lg 20
#define lb(x) x&-x
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,q;
vector<int>E[N];
struct Kruskal{
int f[N],flag;//true->mx;false->mn;
int st[N],ed[N],dfn[N],cnt;
int fa[N][Lg];
vector<int>s[N];
void init(bool opt){
for(int i=1;i<=n;i++)f[i]=i;
flag=opt;
}
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int u,int v){
u=find(u),v=find(v);
if(u==v)return;
f[v]=f[u];
}
void add(int u,int v){
if((flag?v<u:v>u)&&find(u)!=find(v))
s[u].push_back(find(v)),merge(u,v);
}
void dfs(int u){
st[u]=++cnt,dfn[cnt]=u;
for(int i=1;i<Lg;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:s[u])fa[v][0]=u,dfs(v);
ed[u]=cnt;
}
int Subtree(int u,int k){
for(int i=Lg-1;i>=0;i--){
if(fa[u][i]&&(flag?fa[u][i]<=k:fa[u][i]>=k))
u=fa[u][i];
}
return u;
}
}T1,T2;
struct Tree{
int ls,rs,cnt;
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define cnt(x) tr[x].cnt
}tr[N<<5];
int pos[N],rt[N],tot;
int build(int l,int r){
int p=++tot;
if(l==r)return p;
int mid=(l+r)>>1;
ls(p)=build(l,mid);
rs(p)=build(mid+1,r);
return p;
}
int modify(int pre,int l,int r,int x){
int p=++tot;
tr[p]=tr[pre],cnt(p)++;
if(l==r)return p;
int mid=(l+r)>>1;
if(x<=mid)ls(p)=modify(ls(pre),l,mid,x);
else rs(p)=modify(rs(pre),mid+1,r,x);
return p;
}
bool query(int p,int q,int l,int r,int L,int R){
if(L<=l&&r<=R)return cnt(p)^cnt(q);
int mid=(l+r)>>1;bool ret=false;
if(L<=mid)ret|=query(ls(p),ls(q),l,mid,L,R);
if(R>mid)ret|=query(rs(p),rs(q),mid+1,r,L,R);
return ret;
}
int main(){
n=read(),m=read(),q=read();
T1.init(true),T2.init(false);
for(int i=1,u,v;i<=m;i++){
u=read()+1,v=read()+1;
E[u].push_back(v),E[v].push_back(u);
}
for(int u=1;u<=n;u++)
for(int v:E[u])T1.add(u,v);
for(int u=n;u;u--)
for(int v:E[u])T2.add(u,v);
T1.dfs(n),T2.dfs(1);
for(int i=1;i<=n;i++)pos[T1.dfn[i]]=i;
rt[0]=build(1,n);
for(int i=1;i<=n;i++)
rt[i]=modify(rt[i-1],1,n,pos[T2.dfn[i]]);
for(int S,E,L,R;q;q--){
S=read()+1,E=read()+1,L=read()+1,R=read()+1;
S=T2.Subtree(S,L),E=T1.Subtree(E,R);
printf("%d\n",query(rt[T2.ed[S]],rt[T2.st[S]-1],1,n,T1.st[E],T1.ed[E]));
}
return 0;
}