考虑算法

for(ri i=1;i<=m;i++) {
    int x=getfather(e[i].x),y=getfather(e[i].y);
    if(x!=y) {
        father[y]=x;
        ans+=e[i].x;
    }
}

我们这样做可以求出最小/大生成树
如果我们每次合并,都新建一个节点,再与连边,并用并查集合并,就是重构树

void kruskal() {
    sort(e+1,e+m+1,cmp);
    for(ri i=1;i<=2*n-1;i++)father[i]=i;
    now=n;
    for(ri i=1;i<=m;i++) {
        int x=getfather(e[i].x),y=getfather(e[i].y);
        if(x!=y) {
            ++now;
            son[now][0]=x;
            son[now][1]=y;
            father[x]=now,father[y]=now;
            val[now]=e[i].z;
        }
    }
}

性质

总点数
很好证,边最多加条,原点有个,总

二叉树 易得

所有的节点都是叶子 易得

(最小生成树)原图上所有路径中,最大边权最小的那一条路径的边的最大值,就是
最大生成树反之

的扩展,即子树内叶节点可以通过所有的边互相到达

例题


货车运输
https://www.luogu.com.cn/problem/P1967
问题描述 国有座城市,编号从,城市之间有条双向道路。
每一条道路对车辆都有重量限制,简称限重。
现在有辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。


考虑建最大生成树,显然货车走的边一定在最大生成树上
那么建出重构树,即是答案

#include <bits/stdc++.h>
#define ri register int
#define ll long long
using namespace std;
const int Maxn=1e5;
const int Maxm=5e5;
const int Lgn=20;
struct Edge {
    int x,y,z;
}e[Maxm+5];
int val[2*Maxn+5],father[2*Maxn+5],son[2*Maxn+5][2],n,m,q,now;
int dep[2*Maxn+5],st[2*Maxn+5][Lgn+5];
bool cmp(Edge a,Edge b) {
    return a.z>b.z;
}
int getfather(int v) {
    return father[v]==v?v:father[v]=getfather(father[v]);
}
void kruscal() {
    sort(e+1,e+m+1,cmp);
    for(ri i=1;i<=2*n-1;i++)father[i]=i;
    now=n;
    for(ri i=1;i<=m;i++) {
        int x=getfather(e[i].x),y=getfather(e[i].y);
        if(x!=y) {
            ++now;
            son[now][0]=x;
            son[now][1]=y;
            father[x]=now,father[y]=now;
            val[now]=e[i].z;
        }
    }
}
void dfs(int u,int fa) {
    dep[u]=dep[fa]+1;st[u][0]=fa;
    int t=ceil(log2(dep[u]));
    for(ri i=1;i<=t;i++)st[u][i]=st[st[u][i-1]][i-1];
    if(u>n) {
        dfs(son[u][0],u);
        dfs(son[u][1],u);
    }
}
int go_up(int u,int k) {
    int t=ceil(log2(dep[u]));
    for(ri i=0;i<=t;i++)
        if((k>>i)&1)u=st[u][i];
    return u;
}
int lca(int x,int y) {
    if(dep[x]<dep[y])swap(x,y);
    x=go_up(x,dep[x]-dep[y]);
    if(x==y)return x;
    int t=ceil(log2(dep[x]));
    for(ri i=t;i>=0;i--)
        if(st[x][i]!=st[y][i]) {
            x=st[x][i];
            y=st[y][i];
        }
    return st[x][0];
}
int main() {
    scanf("%d%d",&n,&m);
    for(ri i=1;i<=m;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
    kruscal();
    dfs(now,0);
    scanf("%d",&q);
    while(q--) {
        int x,y;
        scanf("%d%d",&x,&y);
        if(getfather(x)!=getfather(y))printf("-1\n");
        else printf("%d\n",val[lca(x,y)]);
    }
    return 0;
}

删边
https://ac.nowcoder.com/acm/contest/5205/E
问题描述
一个个节点条带权边的无向连通图。
次询问,每次询问图中 个互不相同的点,你可以选择一个数,然后将图中所有边权小于等于的边删除。求当删除这些边后个点互不连通时,求的最小值。
注意,本题强制在线


考虑建最大生成树
这里一定要想清楚,不要建错了生成树
我们不可能对于每个点枚举其他点,求
所以分析一下性质
同一条链上,越小,越小
所以,对我们的阈值的限制最强的,其实是最深的那些
那怎么找出最深的
考虑按序排序
画一下图会发现最浅的一定在相邻两个点的之中
很好理解,序记录被访问的时间,更大的如果在同一棵子树中,对答案无影响,如果不在,则会更浅
因为,所以可过

#include <bits/stdc++.h>
#define ri register int
#define ll long long
using namespace std;
const int Maxn=5e5;
const int Lgn=20;
struct Edge {
    int x,y,z;
}e[Maxn+5];
int father[2*Maxn+5];
int a[Maxn+5],n,m,q,vt,now;
int son[2*Maxn+5][2],val[2*Maxn+5],dfn[2*Maxn+5],st[2*Maxn+5][Lgn+5],dep[2*Maxn+5];
static char buf[30000000],*p1=buf,*p2=buf,obuf[30000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x)
{
    x=0;register char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
static char cc[7000000];
template<typename item>
inline void print(register item x)
{ 
    ri len=0;
    do {cc[len++]=x%10+'0',x/=10;}while(x);
    while(len--)putchar(cc[len]);
}
bool cmp1(Edge a,Edge b) {
    return a.z>b.z;
}
bool cmp2(int a,int b) {
    return dfn[a]<dfn[b];
}
int getfather(int v) {
    return father[v]==v?v:father[v]=getfather(father[v]);
}
void kruscal() {
    sort(e+1,e+m+1,cmp1);
    for(ri i=1;i<=2*n-1;i++)father[i]=i;
    now=n;
    for(ri i=1;i<=m;i++) {
        int x=getfather(e[i].x),y=getfather(e[i].y);
        if(x!=y) {
            ++now;
            son[now][0]=x;
            son[now][1]=y;
            father[x]=now,father[y]=now;
            val[now]=e[i].z;
        }
    }
}
void dfs(int u,int fa) {
    dep[u]=dep[fa]+1;st[u][0]=fa;dfn[u]=++vt;
    int t=ceil(log2(dep[u]));
    for(ri i=1;i<=t;i++)st[u][i]=st[st[u][i-1]][i-1];
    if(u>n) {
        dfs(son[u][0],u);
        dfs(son[u][1],u);
    }
}
int go_up(int u,int k) {
    int t=ceil(log2(dep[u]));
    for(ri i=0;i<=t;i++)
        if((k>>i)&1)u=st[u][i];
    return u;
}
int lca(int x,int y) {
    if(dep[x]<dep[y])swap(x,y);
    x=go_up(x,dep[x]-dep[y]);
    if(x==y)return x;
    int t=ceil(log2(dep[x]));
    for(ri i=t;i>=0;i--)
        if(st[x][i]!=st[y][i]) {
            x=st[x][i];
            y=st[y][i];
        }
    return st[x][0];
}
int main() {
    read(n),read(m),read(q);
    for(ri i=1;i<=m;i++)read(e[i].x),read(e[i].y),read(e[i].z);
    kruscal();
    dfs(now,0);
    int ans=0;
    while(q--) {
        int k;
        read(k);
        for(ri i=1;i<=k;i++)read(a[i]),a[i]^=ans;
        sort(a+1,a+k+1,cmp2);
        ans=0;
        for(ri i=2;i<=k;i++)ans=max(ans,val[lca(a[i],a[i-1])]);
        print(ans),putchar('\n');
    }
    fwrite(obuf,p3-obuf,1,stdout);
    return 0;
}


问题描述
本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。

魔力之都可以抽象成一个个节点、条边的无向连通图(节点的编号从)。我们依次用描述一条边的长度、海拔。

作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边。

我们用水位线来描述降雨的程度,它的意义是:所有海拔不超过水位线的边都是有积水的。

是一名来自魔力之都的,刚参加完的他将踏上归程,回到他温暖的家。

的家恰好在魔力之都的号节点。对于接下来天,每一天 都会告诉你他的出发点 ,以及当天的水位线 。

每一天, 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。

可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。

需要特殊说明的是,第二天车会被重置,这意味着:
车会在新的出发点被准备好。 不能利用之前在某处停放的车。 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 进行计算。
强制在线


SPFA死了
考虑坐车可以走到哪些点
我们就是求经过海拔大于的点
性质就是干这事的
建最大重构树,以为边权
我们向上倍增,找到最浅的、海拔大于的点
这个点子树内所有叶节点就是坐车可以到达的节点
那我们把这些点到的最短路取即可
这个可以预处理

#include <bits/stdc++.h>
#define ri register int
#define ll long long
using namespace std;
const int Maxn=2e5;
const int Maxm=4e5;
const int Lgn=20;
const int Inf=2e9+1;
struct Edge {
    int x,y,z;
}e[Maxm+5];
struct Path {
    int u,dis;
};
priority_queue<Path>q;
int lt[Maxn+5],nt[2*Maxm+5],ed[2*Maxm+5],val[2*Maxm+5];
int n,m,cnt,Q,K,S,now,T;
int father[2*Maxn+5],son[2*Maxn+5][2],mindis[2*Maxn+5],dep[2*Maxn+5],st[2*Maxn+5][Lgn+5],v[2*Maxn+5];
int dis[Maxn+5],mark[Maxn+5];
static char buf[30000000],*p1=buf,*p2=buf,obuf[30000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x)
{
    x=0;register char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
static char cc[7000000];
template<typename item>
inline void print(register item x)
{ 
    ri len=0;
    do {cc[len++]=x%10+'0',x/=10;}while(x);
    while(len--)putchar(cc[len]);
}
bool cmp(Edge a,Edge b) {
    return a.z>b.z;
}
bool operator<(Path a,Path b) {
    return a.dis>b.dis;
}
int getfather(int v) {
    return father[v]==v?v:father[v]=getfather(father[v]);
}
void addedge(int x,int y,int z) {
    ed[++cnt]=y;nt[cnt]=lt[x];lt[x]=cnt;
    val[cnt]=z;
}
void kruscal() {
    sort(e+1,e+m+1,cmp);
    for(ri i=1;i<=2*n-1;i++)father[i]=i;
    now=n;
    for(ri i=1;i<=m;i++) {
        int x=getfather(e[i].x),y=getfather(e[i].y);
        if(x!=y) {
            ++now;
            son[now][0]=x;
            son[now][1]=y;
            father[x]=now,father[y]=now;
            v[now]=e[i].z;
        }
    }
}
void init() {
    for(ri i=1;i<=n;i++)dis[i]=Inf,mark[i]=0;
    dis[1]=0;
    q.push((Path){1,0});
}
void dij() {
    while(!q.empty()) {
        Path t=q.top();q.pop();
        int u=t.u;
        if(mark[u])continue;
        mark[u]=1;
        for(ri i=lt[u];i;i=nt[i]) {
            int v=ed[i],l=val[i];
            if(dis[v]>dis[u]+l) {
                dis[v]=dis[u]+l;
                q.push((Path){v,dis[v]});
            }
        }
    }
}
void dfs(int u,int fa) {
    dep[u]=dep[fa]+1;st[u][0]=fa;
    int t=ceil(log2(dep[u]));
    for(ri i=1;i<=t;i++)st[u][i]=st[st[u][i-1]][i-1];
    if(u>n) {
        int x=son[u][0],y=son[u][1];
        dfs(x,u),dfs(y,u);
        mindis[u]=min(mindis[x],mindis[y]);
    }
    else mindis[u]=dis[u];
}
int main() {
    read(T);
    while(T--) {
        read(n),read(m);
        memset(lt,0,sizeof(lt));cnt=0;
        for(ri i=1;i<=m;i++) {
            int x,y,l,a;
            read(x),read(y),read(l),read(a);
            e[i].x=x,e[i].y=y,e[i].z=a;
            addedge(x,y,l);
            addedge(y,x,l);
        }
        kruscal();
        init();
        dij();
        dfs(now,0);
        read(Q),read(K),read(S);
        int ans=0;
        while(Q--) {
            int u,p;
            read(u),read(p);
            u=(u+K*ans-1)%n+1;
            p=(p+K*ans)%(S+1);
            int t=ceil(log2(dep[u]));
            for(ri i=t;i>=0;i--)
                if(v[st[u][i]]>p)u=st[u][i];
            print(ans=mindis[u]);
            putchar('\n');
        }
    }
    fwrite(obuf,p3-obuf,1,stdout);
    return 0;
}

狼人
在日本的茨城县内共有个城市和条道路。这些城市是根据人口数量的升序排列的,依次编号为。每条道路连接两个不同的城市,并且可以双向通行。由这些道路,你能从任意一个城市到另外任意一个城市。

你计划了个行程,这些行程分别编号为。第()个行程是从城市到城市

你是一个狼人。你有两种形态:人形和狼形。在每个行程开始的时候,你是人形。在每个行程结束的时候,你必须是狼形。在行程中,你必须要变身(从人形变成狼形)恰好一次,而且只能在某个城市内(包括可能是在内)变身。

狼人的生活并不容易。当你是人形时,你必须避开人少的城市,而当你是狼形时,你必须避开人多的城市。对于每一次行程(),都有两个阈值(),用以表示哪些城市必须要避开。准确地说,当你是人形时,你必须避开城市;而当你是狼形时,则必须避开城市。这就是说,在行程中你必须在城市 中的其中一个城市内变身。

你的任务是,对每一次行程,判定是否有可能在满足上述限制的前提下,由城市走到城市。你的路线可以有任意长度。


考虑出处理所有能以人形到达的城市,和狼形能到达的城市
对于人形,我们将边的权设为
这样,我们建最大重构树,向上倍增到最浅的边权大于等于的点,子树内的叶子就是可以到的城市
狼性边权改为,做最小生成树,同样倍增
那么我们要看这两个城市集合有没有交集
显然维护集合是不行的,那么考虑一个城市有没有出现在这两个集合里面
注意到若城市在两个集合里,则一定在倍增到的点的子树里
也就是说,设为点第一/二棵里的序,设为倍增到的点
城市满足



典型的二位偏序
那么有两种方法
一种是离线+树状数组
第二种是主席树,以第一棵的序为版本,第二棵的序为区间
将一个路线看做一个询问,问在这个范围内的点有没有,有就是可行的

#include <bits/stdc++.h>
#define ri register int
#define ll long long
using namespace std;
const int Maxn=2e5;
const int Maxm=4e5;
const int Lgn=20;
struct Edge {
    int x,y,z;
}e[Maxm+5];
struct Query {
    int x,y,id,k;
}q[Maxn*4+5];
struct Point {
    int x,y;
}p[Maxn+5];
int ans[Maxn+5],c[2*Maxn+5],n,m,Q,vt,tot;
int father[2*Maxn+5];
int val[2][2*Maxn+5],son[2][2*Maxn+5][2],st[2][2*Maxn+5][Lgn+5],dep[2][2*Maxn+5],in[2][2*Maxn+5],out[2][2*Maxn+5];
bool cmp1(Edge a,Edge b) {
    return a.z>b.z;
}
bool cmp2(Edge a,Edge b) {
    return a.z<b.z;
}
bool cmp3(Query a,Query b) {
    return a.x<b.x;
}
bool cmp4(Point a,Point b) {
    return a.x<b.x;
}
int lowbit(int x) {return x&(-x);}
void add(int x,int d) {for(;x<=2*n-1;x+=lowbit(x))c[x]+=d;}
int getsum(int x) {int ret=0;for(;x>=1;x-=lowbit(x))ret+=c[x];return ret;}
int getfather(int v) {
    return father[v]==v?v:father[v]=getfather(father[v]);
}
void dfs(int u,int k,int fa) {
    in[k][u]=++vt;
    dep[k][u]=dep[k][fa]+1;st[k][u][0]=fa;
    int s=ceil(log2(dep[k][u]));
    for(ri i=1;i<=s;i++)st[k][u][i]=st[k][st[k][u][i-1]][i-1];
    if(u>n) {
        int x=son[k][u][0],y=son[k][u][1];
        dfs(x,k,u),dfs(y,k,u);
    }
    out[k][u]=vt;
}
void kruscal() {
    for(ri i=1;i<=m;i++)e[i].z=min(e[i].x,e[i].y);
    sort(e+1,e+m+1,cmp1);
    for(ri i=1;i<=n;i++)father[i]=i;
    int now=n;
    for(ri i=1;i<=m;i++) {
        int x=getfather(e[i].x),y=getfather(e[i].y);
        if(x!=y) {
            ++now;father[now]=now;
            son[0][now][0]=x;
            son[0][now][1]=y;
            father[x]=now,father[y]=now;
            val[0][now]=e[i].z;
        }
    }
    dfs(now,0,0);
    for(ri i=1;i<=m;i++)e[i].z=max(e[i].x,e[i].y);
    sort(e+1,e+m+1,cmp2);
    for(ri i=1;i<=n;i++)father[i]=i;
    now=n;
    for(ri i=1;i<=m;i++) {
        int x=getfather(e[i].x),y=getfather(e[i].y);
        if(x!=y) {
            ++now;father[now]=now;
            son[1][now][0]=x;
            son[1][now][1]=y;
            father[x]=now,father[y]=now;
            val[1][now]=e[i].z;
        }
    }
    vt=0;
    dfs(now,1,0);
}
void make_query(int u,int v,int id) {
    int x1=in[0][u],y1=in[1][v],x2=out[0][u],y2=out[1][v];
    q[++tot]=(Query){x1-1,y1-1,id,1};
    q[++tot]=(Query){x2,y1-1,id,-1};
    q[++tot]=(Query){x1-1,y2,id,-1};
    q[++tot]=(Query){x2,y2,id,1};
}
int main() {
    scanf("%d%d%d",&n,&m,&Q);
    for(ri i=1;i<=m;i++) {
        scanf("%d%d",&e[i].x,&e[i].y);
        ++e[i].x,++e[i].y;
    }
    kruscal();
    val[1][0]=n+1;
    for(ri i=1;i<=Q;i++) {
        int x,y,l,r;
        scanf("%d%d%d%d",&x,&y,&l,&r);
        ++x,++y,++l,++r;
        int s=ceil(log2(dep[0][x]));
        for(ri j=s;j>=0;j--)
            if(val[0][st[0][x][j]]>=l)x=st[0][x][j];
        s=ceil(log2(dep[1][y]));
        for(ri j=s;j>=0;j--)
            if(val[1][st[1][y][j]]<=r)y=st[1][y][j];
        make_query(x,y,i);
    }
    for(ri i=1;i<=n;i++)p[i].x=in[0][i],p[i].y=in[1][i];
    sort(q+1,q+tot+1,cmp3);
    sort(p+1,p+n+1,cmp4);
    int now=1;
    for(ri i=1;i<=tot;i++) {
        while(p[now].x<=q[i].x&&now<=n)add(p[now].y,1),++now;
        ans[q[i].id]+=getsum(q[i].y)*q[i].k;
    }
    for(ri i=1;i<=Q;i++)printf("%d\n",ans[i]?1:0);
    return 0;
}