Skip to content

kruscal重构树 ​

模板 ​

  • 如果是最小生成树,那么u,v之间路径的最小边权最大值,即为lca(u,v)的权值
  • 如果是最大生成树,那么为最大边权最小值
cpp
void Ex_kruscal(){
    sort(e.begin(),e.end());
    dsu.init(2*n+1);
    for(auto [u,v,c]:e){
        if(dsu.same(u,v))continue;
        ++idx;
        u=dsu.find(u),v=dsu.find(v);
        adj[idx].push_back(u);
        adj[idx].push_back(v);
        dsu.merge(idx,u);
        dsu.merge(idx,v);
        w[idx]=c;
    }
}

P4197 Peaks ​

题意:

n座山峰,每座山峰高度为hi

m条路径,每条路径有困难度w

q组询问,v x k求从v开始只经过小于等于x的边能到达的点,山峰第k大值

思路:

  • 重构树,倍增找出第一个小于等于x的祖先
  • 接下来就是求第k小的问题,建出dfs序,主席树查询
cpp
struct Edge{
    int u,v,w;
    bool operator<(const Edge&tmp)const{
        return w<tmp.w;
    }
};
struct DSU{
    vector<int> par;
    void init(int n){
        par.resize(n,0);
        iota(par.begin(),par.end(),0);
    }
    int find(int x){
        return par[x]==x?x:par[x]=find(par[x]);
    }
    bool merge(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return false;
        par[y]=x;
        return true;
    }
    bool same(int x,int y){
        return find(x)==find(y);
    }
} dsu;
int idx,n,m,q,ts,w[N],dfn[N],siz[N],nw[N],h[N],par[N][30];
vector<int> adj[N];
vector<Edge> e;
void Ex_kruscal(){
    sort(e.begin(),e.end());
    dsu.init(2*n+1);
    for(auto [u,v,c]:e){
        if(dsu.same(u,v))continue;
        ++idx;
        u=dsu.find(u),v=dsu.find(v);
        adj[idx].push_back(u);
        adj[idx].push_back(v);
        dsu.merge(idx,u);
        dsu.merge(idx,v);
        w[idx]=c;
    }
}
void dfs(int u,int fa){
    dfn[u]=++ts;
    if(adj[u].size())nw[ts]=-1;
    else nw[ts]=h[u];
    par[u][0]=fa;
    for(int i=1;i<=25;i++)par[u][i]=par[par[u][i-1]][i-1];
    siz[u]=1;
    for(auto v:adj[u]){
        dfs(v,u);
        siz[u]+=siz[v];
    }
}
struct Node{
    int ls,rs,cnt;
};
struct SegmentTree{
    vector<Node> t;
    void init(int n){
        t=vector<Node>(n<<5);
    }
    void pushup(int p){
        t[p].cnt=t[t[p].ls].cnt+t[t[p].rs].cnt;
    }
    int build(int l,int r){
        int p=++idx;
        if(l==r)return p;
        int mid=l+r>>1;
        t[p].ls=build(l,mid);
        t[p].rs=build(mid+1,r);
        return p;
    }
    int insert(int p,int l,int r,int x){
        int q=++idx;
        t[q]=t[p];
        if(l==r){
            t[q].cnt++;
            return q;
        }
        int mid=l+r>>1;
        if(x<=mid)t[q].ls=insert(t[p].ls,l,mid,x);
        else t[q].rs=insert(t[p].rs,mid+1,r,x);
        pushup(q);
        return q;
    }
    int query(int p,int q,int l,int r,int k){
        if(l==r)return l;
        int mid=l+r>>1;
        int cnt=t[t[q].ls].cnt-t[t[p].ls].cnt;
        if(k<=cnt)return query(t[p].ls,t[q].ls,l,mid,k);
        return query(t[p].rs,t[q].rs,mid+1,r,k-cnt);
    }
} seg;
void solve(){
    cin>>n>>m>>q;
    idx=n;
    vector<int> nums;
    for(int i=1;i<=n;i++)cin>>h[i],nums.push_back(h[i]);
    sort(nums.begin(),nums.end());
    nums.erase(unique(nums.begin(),nums.end()),nums.end());
    for(int i=1;i<=n;i++)h[i]=lower_bound(nums.begin(),nums.end(),h[i])-nums.begin();
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        e.push_back({u,v,w});
    }
    Ex_kruscal();
    dfs(idx,0);
    int NN=nums.size()-1;
    seg.init(NN);
    vector<int> root(ts+1);
    root[0]=seg.build(0,NN);
    for(int i=1;i<=ts;i++){
        if(nw[i]!=-1)root[i]=seg.insert(root[i-1],0,NN,nw[i]);
        else root[i]=root[i-1];
    }
    while(q--){
        int v,x,k;
        cin>>v>>x>>k;
        for(int i=25;i>=0;i--){
            if(par[v][i]&&w[par[v][i]]<=x)v=par[v][i];
        }
        int l=dfn[v],r=dfn[v]+siz[v]-1;
        int cnt=seg.t[root[r]].cnt-seg.t[root[l-1]].cnt;
        if(cnt<k){
            cout<<"-1\n";
            continue;
        }
        k=cnt-k+1;
        cout<<nums[seg.query(root[l-1],root[r],0,NN,k)]<<"\n";
    }
}

P1967 [NOIP2013 提高组] 货车运输 ​

题意:

每次询问,求两个节点的最大路径最小

思路:

最大生成树

cpp
struct Edge{
    int u,v,w;
    bool operator<(const Edge&tmp)const{
        return w>tmp.w;
    }
};
struct DSU{
    vector<int> par;
    void init(int n){
        par.resize(n,0);
        iota(par.begin(),par.end(),0);
    }
    int find(int x){
        return par[x]==x?x:par[x]=find(par[x]);
    }
    bool merge(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return false;
        par[y]=x;
        return true;
    }
    bool same(int x,int y){
        return find(x)==find(y);
    }
} dsu;
int idx,n,m,q,w[N],par[N][30],dep[N];
vector<int> adj[N];
vector<Edge> e;
void Ex_kruscal(){
    sort(e.begin(),e.end());
    dsu.init(2*n+1);
    for(auto [u,v,c]:e){
        if(dsu.same(u,v))continue;
        ++idx;
        u=dsu.find(u),v=dsu.find(v);
        adj[idx].push_back(u);
        adj[idx].push_back(v);
        dsu.merge(idx,u);
        dsu.merge(idx,v);
        w[idx]=c;
    }
}
void dfs(int u,int fa){
    dep[u]=dep[fa]+1;
    par[u][0]=fa;
    for(int i=1;i<=__lg(dep[u]);i++)par[u][i]=par[par[u][i-1]][i-1];
    for(auto v:adj[u])dfs(v,u);
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	while(dep[u]>dep[v]){
		u=par[u][(int)__lg(dep[u]-dep[v])];
	}
	if(u==v)return u;
	for(int i=log2(dep[u]);i>=0;i--){
		if(par[u][i]!=par[v][i])u=par[u][i],v=par[v][i];
	}
	return par[u][0];
}
void solve(){
    cin>>n>>m;
    idx=n;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        e.push_back({u,v,w});
    }
    Ex_kruscal();
    for(int i=1;i<=idx;i++){
        if(dsu.find(i)==i)dfs(i,0);
    }
    cin>>q;
    while(q--){
        int x,y;
        cin>>x>>y;
        if(!dsu.same(x,y)){
            cout<<"-1\n";
            continue;
        }
        cout<<w[lca(x,y)]<<"\n";
    }
}

P4768 [NOI2018] 归程 ​

题意:

n个点m条边,每条边有海拔a和边长l,家在点1

如果当前边海拔不高于水位,则只能步行

q次询问,要求在线,每次询问为v p表示当天水位为p,从v出发回家的最小步行距离

思路:

  • 先对每个点求一次从1开始的dijkstra
  • 建立最大生成树,重构树
  • 找出深度最浅的大于等于p的点,那么该点的子树可以任意到达,对子树的点最短距离求min即可
cpp
struct Edge{
    int u,v,w;
    bool operator<(const Edge&tmp)const{
        return w>tmp.w;
    }
};
struct DSU{
    vector<int> par;
    void init(int n){
        par.resize(n,0);
        iota(par.begin(),par.end(),0);
    }
    int find(int x){
        return par[x]==x?x:par[x]=find(par[x]);
    }
    bool merge(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return false;
        par[y]=x;
        return true;
    }
    bool same(int x,int y){
        return find(x)==find(y);
    }
} dsu;
int idx,n,m,q,w[N],dis[N],par[N][30],dep[N],ANS[N];
bool vis[N];
vector<int> adj[N];
vector<PII> g[N];
vector<Edge> e;
void Ex_kruscal(){
    sort(e.begin(),e.end());
    dsu.init(2*n+1);
    for(auto [u,v,c]:e){
        if(dsu.same(u,v))continue;
        ++idx;
        u=dsu.find(u),v=dsu.find(v);
        adj[idx].push_back(u);
        adj[idx].push_back(v);
        dsu.merge(idx,u);
        dsu.merge(idx,v);
        w[idx]=c;
    }
}
void dfs(int u,int fa){
    dep[u]=dep[fa]+1;
    par[u][0]=fa;
    for(int i=1;i<=__lg(dep[u]);i++)par[u][i]=par[par[u][i-1]][i-1];
    for(auto v:adj[u]){
        dfs(v,u);
        ANS[u]=min(ANS[u],ANS[v]);
    }
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	while(dep[u]>dep[v]){
		u=par[u][(int)__lg(dep[u]-dep[v])];
	}
	if(u==v)return u;
	for(int i=log2(dep[u]);i>=0;i--){
		if(par[u][i]!=par[v][i])u=par[u][i],v=par[v][i];
	}
	return par[u][0];
}
void dijk(){
    priority_queue<PII,vector<PII>,greater<PII>> q;
    for(int i=0;i<=n;i++)dis[i]=1e18,vis[i]=0;
    dis[1]=0;
    q.push({0,1});
    while(!q.empty()){
        auto [d,u]=q.top();
        q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(auto [v,c]:g[u]){
            if(dis[v]>dis[u]+c){
                dis[v]=dis[u]+c;
                q.push({dis[v],v});
            }
        }
    }
}
void solve(){
    cin>>n>>m;
    idx=n;
    for(int i=1;i<=m;i++){
        int u,v,w,l;
        cin>>u>>v>>l>>w;
        g[u].push_back({v,l});
        g[v].push_back({u,l});
        e.push_back({u,v,w});
    }
    dijk();
    Ex_kruscal();
    for(int i=0;i<=idx;i++){
        ANS[i]=1e18;
        if(i<=n)ANS[i]=dis[i];
    }
    dfs(idx,0);
    int ans=0;
    int Q,K,S;
    cin>>Q>>K>>S;
    while(Q--){
        int v,p;
        cin>>v>>p;
        v=(v+K*ans-1)%n+1;
        p=(p+K*ans)%(S+1);
        for(int i=25;i>=0;i--){
            if(par[v][i]&&w[par[v][i]]>p)v=par[v][i];
        }
        ans=ANS[v];
        cout<<ans<<'\n';
    }
    e.clear();
    for(int i=0;i<=idx;i++){
        adj[i].clear(),g[i].clear();
        w[i]=dis[i]=dep[i]=ANS[i]=vis[i]=0;
        for(int j=0;j<30;j++)par[i][j]=0;
    }
}

AGC002D ​

题意:

n个点m条边,每次询问x y z表示从x和y出发经过z个点,边的最大编号最小

思路:

  • 最小生成树重构树
  • 每次二分答案
  • 那么上界就有了,直接往上跳
  • 注意x和y如果跳到同一个点只算一次
cpp
struct Edge{
    int u,v,w;
    bool operator<(const Edge&tmp)const{
        return w<tmp.w;
    }
};
struct DSU{
    vector<int> par;
    void init(int n){
        par.resize(n,0);
        iota(par.begin(),par.end(),0);
    }
    int find(int x){
        return par[x]==x?x:par[x]=find(par[x]);
    }
    bool merge(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return false;
        par[y]=x;
        return true;
    }
    bool same(int x,int y){
        return find(x)==find(y);
    }
} dsu;
int idx,n,m,q,w[N],par[N][30],dep[N],sum[N];
bool vis[N];
vector<int> adj[N];
vector<PII> g[N];
vector<Edge> e;
void Ex_kruscal(){
    sort(e.begin(),e.end());
    dsu.init(2*n+1);
    for(auto [u,v,c]:e){
        if(dsu.same(u,v))continue;
        ++idx;
        u=dsu.find(u),v=dsu.find(v);
        adj[idx].push_back(u);
        adj[idx].push_back(v);
        dsu.merge(idx,u);
        dsu.merge(idx,v);
        w[idx]=c;
    }
}
void dfs(int u,int fa){
    dep[u]=dep[fa]+1;
    par[u][0]=fa;
    for(int i=1;i<=__lg(dep[u]);i++)par[u][i]=par[par[u][i-1]][i-1];
    for(auto v:adj[u])dfs(v,u),sum[u]+=sum[v];
}
int lca(int u,int v){
	if(dep[u]<dep[v])swap(u,v);
	while(dep[u]>dep[v]){
		u=par[u][(int)__lg(dep[u]-dep[v])];
	}
	if(u==v)return u;
	for(int i=log2(dep[u]);i>=0;i--){
		if(par[u][i]!=par[v][i])u=par[u][i],v=par[v][i];
	}
	return par[u][0];
}
bool check(int mid,int x,int y,int z){
    for(int i=25;i>=0;i--){
        if(par[x][i]&&w[par[x][i]]<=mid)x=par[x][i];
        if(par[y][i]&&w[par[y][i]]<=mid)y=par[y][i];
    }
    if(x==y)return sum[x]>=z;
    return sum[x]+sum[y]>=z;
}
void solve(){
    cin>>n>>m;
    idx=n;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        e.push_back({u,v,i});
    }
    Ex_kruscal();
    for(int i=1;i<=n;i++)sum[i]=1;
    dfs(idx,0);
    int q;
    cin>>q;
    while(q--){
        int x,y,z;
        cin>>x>>y>>z;
        int l=1,r=m;
        while(l<r){
            int mid=l+r>>1;
            if(check(mid,x,y,z))r=mid;
            else l=mid+1;
        }
        cout<<l<<"\n";
    }
}