zoukankan      html  css  js  c++  java
  • 【洛谷6765】[APIO2020] 交换城市(Kruskal生成树)

    点此看题面

    • 给定一张(n)个点(m)条边的无向图。
    • (q)次询问,每次给定两个点。让你找到一种走法,在两点不相遇的前提下交换两点位置,使得所经边权的最大值最小。
    • (nle10^5,m,qle2 imes10^5),强制在线

    一开始想直接根据推出的性质大力分类讨论。

    写了(150)多行之后发现有一个地方完全写错了,一怒之下直接弃掉了,果然现在根本写不来细节题。。。

    后来发现(Kruskal)重构树又好想又好写,于是果断选择了(Kruskal)重构树。

    至于什么是(Kruskal)重构树,可见这篇博客:Kruskal重构树补记

    暴力二分答案

    首先,考虑暴力二分答案(x),那么就相当于每次保留边权小于等于(x)的边验证在这张新图上是否能完成交换。

    容易发现,除非这张图是一条链,否则必然存在一种可行的方案。(具体证明自己画画图就好了,应该是比较显然的)

    但如果每次询问都二分一次答案显然不可行,需要用一些更高级的东西。

    (Kruskal)重构树

    考虑(Kruskal)重构树上倍增本质上就是一个二分答案的过程。

    我们只要记录一下树上每个点的子树在原图上是不是一条链。

    回忆(Kruskal)重构树的基本性质:从(x)出发只经过边权大于等于/小于等于(v)的边所能到达的点集,就是(x)深度最小点权大于等于(v)/小于等于(v)的祖先子树内所有的叶节点。

    因此我们只要找到询问两点(LCA)深度最大的子树不是一条链的点,它的点权便是答案。

    那么现在的问题就是如何判断一个点子树在原图上是不是一条链。

    每加入一条边,我们分类讨论:

    • 如果它的两个端点已经连通,那么加上这个点之后必然不可能是链。在一般的(Kruskal)重构树上我们不会对应这种无用边建点,但此题中我们需要给它建一个点,并标记这个点子树内不是一条链。
    • 如果它的两个端点没有联通,那么仅当它的两个端点所在的连通块原本都是一条链,且这两个端点都是各自所在链的首/尾,新的连通块才是一条链。为了处理这个判断,对于每条链还要记录一下它的首尾。

    建出树之后,询问只要倍增跳就可以解掉了。

    代码:(O((m+q)logn))

    #include "swap.h"
    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define M 200000
    #define LN 20
    #define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    using namespace std;
    int n,m;struct line {int x,y,v;I bool operator < (Con line& o) Con {return v<o.v;}}s[M+5];
    int rt,X[N+M+5],Y[N+M+5],ee,lnk[N+M+5];struct edge {int to,nxt;}e[2*M+5];
    int fa[N+M+5];I int getfa(CI x) {return fa[x]?fa[x]=getfa(fa[x]):x;}//并查集维护连通性
    int D[N+M+5],T[N+M+5],V[N+M+5],f[N+M+5][LN+5];I void dfs(CI x)
    {
    	RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];//预处理倍增数组
    	for(i=lnk[x];i;i=e[i].nxt) D[e[i].to]=D[f[e[i].to][0]=x]+1,dfs(e[i].to);
    }
    I int LCA(RI x,RI y)//倍增LCA
    {
    	RI i;D[x]<D[y]&&(swap(x,y),0);
    	for(i=0;D[x]^D[y];++i) (D[x]^D[y])>>i&1&&(x=f[x][i]);if(x==y) return x;
    	for(i=LN;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
    }
    I int Jump(RI x)//倍增上跳,找到深度最大的子树不是一条链的点
    {
    	for(RI i=LN;~i;--i) !T[f[x][i]]&&(x=f[x][i]);return T[x]?x:f[x][0];
    }
    void init(int _n,int _m,vector<int> _x,vector<int> _y,vector<int> _v)//初始化
    {
    	RI i;for(n=_n,m=_m,i=1;i<=m;++i) s[i].x=_x[i-1]+1,s[i].y=_y[i-1]+1,s[i].v=_v[i-1];
    	RI o,x,y,A,B;for(i=1;i<=n;++i) X[i]=Y[i]=i;for(sort(s+1,s+m+1),i=1;i<=m;++i)//Kruskal
    	{
    		V[o=n+i]=s[i].v,x=getfa(s[i].x),y=getfa(s[i].y);
    		if(x==y) {T[o]=1,add(o,x),fa[x]=o;continue;}//如果已连通,新建一个点,标记不是链
    		if(add(o,x),add(o,y),fa[x]=fa[y]=o,T[o]=T[x]|T[y]) continue;//如果两个端点中有至少一个所在连通块不是一条链
    		s[i].x==X[x]&&(X[o]=Y[x]),s[i].x==Y[x]&&(X[o]=X[x]),
    		s[i].y==X[y]&&(Y[o]=Y[y]),s[i].y==Y[y]&&(Y[o]=X[y]),(!X[o]||!Y[o])&&(T[o]=1);//如果有至少一个不是链的首尾
    	}dfs(n+m),T[0]=1;
    }
    int getMinimumFuelCapacity(int x,int y) {RI t=Jump(LCA(x+1,y+1));return t?V[t]:-1;}//询问
    
  • 相关阅读:
    把一件简单的事情做好你就不简单了
    一个经验尚浅的码农五年软件开发的一点自我总结,对工作五年的反思~
    我就是一名房地产经纪人!不是中介,谁能明白我们呢?
    我与父辈的酒局
    郎意难坚,侬情自热(文/王路)
    红灯须硬闯,马路要横穿(文/王路)
    孩子,你慢慢来
    职场六年后的一点点感言
    有幸见到一朵花的绽放
    当你遇到她
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6765.html
Copyright © 2011-2022 走看看