uva11354 最小瓶颈路及其应用

关键词:LCA,最小瓶颈路,树上路径问题、次小生成树

询问任意两点间最小瓶颈路(最大边最小)

法一:

1.dp[u][v]:MST上u,v路径最小瓶颈路 O(n^2)

2.每组询问输出dp[u][v] O(q)

本题n最大可取50000,此法不可取

法二:

1.anc[u][i]:MST有根树中,u的第2^i个祖先;maxcost[u][i]:u到第2^i个祖先路径上的瓶颈(最长边) O(n*logn)

2.每组询问,查找u和v分别到它们公共祖先路径上的瓶颈的最大值 O(logn*q)

询问复杂度上升,但是可以接受。

心得:树上路径问题。先转化为有根树,再用LCA解决多组询问问题(前提是一般dp的预处理复杂度较高,无法完成,而LCA预处理复杂度较低

应用:边权减小,询问MST变化(n很大,无法O(n^2)预处理)


变题:给定一棵树,每个顶点有一个权值,询问某两点路径上的最大权值(n<=50000,q<=100000)

1.建立有根树,边权定义为子节点的点权,只有根节点附加权值 O(n)

2.同上求anc[u][i]和maxcost[u][i] O(n*logn)

3.询问时,加一个特判,如果两点公共祖先是根节点,则需要比较ans和根节点权值的大小,取较大值即可 O(q*logn)



#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<math.h>
#define ll long long
#define sf scanf
#define pf printf
#define INF 1<<29
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define maxn 50010
#define maxm 100010
const ll mol=1000000007;
using namespace std;

int n,m,q;
struct Node{
    int u,v,w;
    bool operator<(const Node&rhs)const{
        return w<rhs.w;
    }
}node[maxm<<1];
struct Edge{
    int to,next,w;
}edge[maxn<<2];
int head[maxn],tot,mst;
int p[maxn];
int father[maxn],depth[maxn],cost[maxn];
int anc[maxn][30],maxcost[maxn][30];

void add(int u,int v,int w){
    edge[tot].to=v,edge[tot].w=w,edge[tot].next=head[u],head[u]=tot++;
}

int Find(int x){ return (p[x]==x)?x:Find(p[x]); }

void kruscal(){
    for(int i=1;i<=n;i++) p[i]=i;
    sort(node+1,node+m+1);
    mem(head,-1),tot=0,mst=0;
    for(int i=1;i<=m;i++){
        int u=node[i].u,v=node[i].v,w=node[i].w;
        int x=Find(u),y=Find(v);
        if(x!=y){
            p[x]=y;mst+=w;
            add(u,v,w),add(v,u,w);
        }
    }
}

void root(int u,int fa,int w){
    father[u]=fa,cost[u]=w;
    if(fa!=-1)  depth[u]=depth[fa]+1;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=edge[i].w;
        if(v!=fa) root(v,u,w);
    }
}

void preprocess(){
    for(int i=1;i<=n;i++){//大括号位置打错了TAT...
        anc[i][0]=father[i],maxcost[i][0]=cost[i];
        for(int j=1;(1<<j)<=n;j++) anc[i][j]=-1;
    }
        for(int j=1;(1<<j)<=n;j++)
            for(int i=1;i<=n;i++)
                if(anc[i][j-1]!=-1){//判断条件很重要!
                    anc[i][j]=anc[anc[i][j-1]][j-1];
                    maxcost[i][j]=max(maxcost[i][j-1],maxcost[anc[i][j-1]][j-1]);
                }
}

int query(int u,int v){
    if(depth[u]<depth[v]) swap(u,v);//交换u,v,而不是交换u,v的深度
    int log,ans=-INF;//记录u深度的二进制最大位数
    for(log=0;(1<<log)<=depth[u];log++); log--;
    for(int i=log;i>=0;i--)//上升到同一高度
        if(depth[u]-(1<<i)>=depth[v]) { ans=max(ans,maxcost[u][i]);u=anc[u][i]; }
    if(u==v) return ans;//v是u的祖先---特判!!!
    for(int i=log;i>=0;i--)//同步上升到公共祖先节点的子节点
        if(anc[u][i]!=-1&&anc[u][i]!=anc[v][i]){
            ans=max(ans,maxcost[u][i]),u=anc[u][i];
            ans=max(ans,maxcost[v][i]),v=anc[v][i];
        }
    ans=max(ans,cost[u]),ans=max(ans,cost[v]);
    return ans;
}

int main(){
    //freopen("a.txt","r",stdin);
    int t=0;
    while(scanf("%d%d",&n,&m)!=EOF){
        if(t!=0) printf("\n");//输出问题---最后一组不能输出换行,换行必须写在前面
        t++;
        for(int i=1;i<=m;i++) scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
        kruscal();
        depth[1]=0; root(1,-1,0);
        preprocess();
        scanf("%d",&q);
        while(q--){
            int u,v; scanf("%d%d",&u,&v);
            printf("%d\n",query(u,v));
        }
    }
}


利用最小瓶颈路结论可以求出次小生成树

O(nlogn)预处理,O(mlogn)询问

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#define maxm 1000000
#define maxn 110
#define rad 10000
#define INF 1<<28
#define ll long long
#define rad 10000
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

int t,n,m,mst;

struct Node{
    int u,v,w;
    bool operator<(const Node&rhs)const{
        return w<rhs.w;
    }
}node[maxn*maxn];

struct Edge{
    int to,next,w;
}edge[maxn*maxn];
int head[maxn],tot,p[maxn],vis[maxn][maxn],fa[maxn],cost[maxn];
int maxc[maxn][20],maxc2[maxn][20],anc[maxn][20],depth[maxn];

void add(int u,int v,int w) { edge[tot].to=v,edge[tot].next=head[u],edge[tot].w=w,head[u]=tot++; }

int Find(int u) { return p[u]==u?u:(p[u]=Find(p[u])); }

void kruscal(){
    for(int i=1;i<=n;i++) p[i]=i;
    mst=0,mem(vis,0),mem(head,-1),tot=0;
    for(int i=1;i<=m;i++){
        int x=Find(node[i].u),y=Find(node[i].v);
        if(x!=y){
            p[x]=y,mst+=node[i].w;
            vis[node[i].u][node[i].v]=vis[node[i].v][node[i].u]=1;
            add(node[i].u,node[i].v,node[i].w),add(node[i].v,node[i].u,node[i].w);
        }
    }
}

void dfs(int u,int f,int w){
    cost[u]=w,fa[u]=f,depth[u]=(f==-1)?0:(depth[f]+1);
    for(int i=i=head[u];i!=-1;i=edge[i].next)
        if(edge[i].to!=f) dfs(edge[i].to,u,edge[i].w);
}

void process(){
    for(int i=1;i<=n;i++){
        anc[i][0]=fa[i],maxc[i][0]=cost[i],maxc2[i][0]=cost[i];
        for(int j=1;(1<<j)<n;j++) anc[i][j]=-1;
    }
    for(int j=1;(1<<j)<n;j++)
        for(int i=1;i<=n;i++)
            if(anc[i][j-1]!=-1){
                int a=anc[i][j-1];
                anc[i][j]=anc[a][j-1];
                maxc[i][j]=max(maxc[i][j-1],maxc[a][j-1]);
            }
}

int query(int u,int v){
    int tmp,log,i;
    if(depth[u]<depth[v]) swap(u,v);
    for(log=1;(1<<log)<=depth[u];log++); log--;
    int ans=-INF;
    for(int i=log;i>=0;i--) if(depth[u]-depth[v]>=(1<<i)) { ans=max(ans,maxc[u][i]);u=anc[u][i]; }
    if(u==v) return ans;
    for(int i=log;i>=0;i--)
        if(anc[u][i]!=-1&&anc[u][i]!=anc[v][i]){
            ans=max(maxc[u][i],ans),u=anc[u][i];
            ans=max(maxc[v][i],ans),v=anc[v][i];
        }
    ans=max(ans,cost[u]),ans=max(ans,cost[v]);
    return ans;
}

int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
        sort(node+1,node+m+1);
        kruscal();
        dfs(1,-1,0);//转化为有根树
        process();//倍增法预处理x的第i个祖先及路径上的最长边和次长边
        int ans=INF;
        for(int i=1;i<=m;i++){
            int u=node[i].u,v=node[i].v,w=node[i].w;
            if(!vis[u][v]){
                int tmp=mst+w-query(u,v);
                if(tmp<ans) ans=tmp;
            }
        }
        printf("%d %d\n",mst,ans);
    }
}

求出最长边和严格次长边解决严格次小生成树问题

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#define maxm 500000
#define maxn 100100
#define rad 10000
#define INF 1<<28
#define ll long long
#define rad 10000
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;

int t,n,m,mst;

struct Node{
    int u,v,w;
    bool operator<(const Node&rhs)const{
        return w<rhs.w;
    }
}node[maxm<<1];

struct Edge{
    int to,next,w;
}edge[maxm<<1];
int head[maxn],tot,p[maxn],fa[maxn],cost[maxn];
int maxc[maxn][20],maxc2[maxn][20],anc[maxn][20],depth[maxn];
int max1,max2;
bool vis[maxm];

void add(int u,int v,int w) { edge[tot].to=v,edge[tot].next=head[u],edge[tot].w=w,head[u]=tot++; }

int Find(int u) { return p[u]==u?u:(p[u]=Find(p[u])); }

void kruscal(){
    for(int i=1;i<=n;i++) p[i]=i;
    mst=0,mem(vis,0),mem(head,-1),tot=0;
    for(int i=1;i<=m;i++){
        int x=Find(node[i].u),y=Find(node[i].v);
        if(x!=y){
            p[x]=y,mst+=node[i].w,vis[i]=1;
            add(node[i].u,node[i].v,node[i].w),add(node[i].v,node[i].u,node[i].w);
        }
    }
}

void dfs(int u,int f,int w){
    cost[u]=w,fa[u]=f,depth[u]=(f==-1)?0:(depth[f]+1);
    for(int i=i=head[u];i!=-1;i=edge[i].next)
        if(edge[i].to!=f) dfs(edge[i].to,u,edge[i].w);
}

void process(){
    for(int i=1;i<=n;i++){
        anc[i][0]=fa[i],maxc[i][0]=cost[i],maxc2[i][0]=0;
        for(int j=1;(1<<j)<n;j++) anc[i][j]=-1;
    }
    for(int j=1;(1<<j)<n;j++)
        for(int i=1;i<=n;i++)
            if(anc[i][j-1]!=-1){
                int a=anc[i][j-1];
                anc[i][j]=anc[a][j-1];
                maxc[i][j]=max(maxc[i][j-1],maxc[a][j-1]);
                if(maxc[i][j-1]==maxc[a][j-1]) maxc2[i][j]=max(maxc2[i][j-1],maxc2[a][j-1]);
                else if(maxc[i][j-1]>maxc[a][j-1]) maxc2[i][j]=max(maxc2[i][j-1],maxc[a][j-1]);
                else maxc2[i][j]=max(maxc[i][j-1],maxc2[a][j-1]);
            }
}

void cal(int u,int i){
    u=anc[u][i],max1=max(max1,maxc[u][i]);
    if(max1==maxc[u][i]) max2=max(max2,maxc2[u][i]);
    else if(max1<maxc[u][i]) max2=max(max1,maxc2[u][i]);
    else max2=max(max2,maxc[u][i]);
}

void cal2(int x){
    if(x>max2&&x<max1) max2=x;
    if(x>max1) max2=max1,max1=x;
}

void query(int u,int v){
    if(depth[u]<depth[v]) swap(depth[u],depth[v]);
    int d=depth[u]-depth[v],tmp=0;
    for(int i=0;i<16;i++) if(d&(1<<i)) cal(u,i);//更新路径最大和严格次大值
    //for(int i=16;i>=0;i--) if((tmp+=(1<<i))<=d) cal(u);//另一种写法
    if(u==v) return;
    for(int i=16;i>=0;i--) if(anc[u][i]!=-1&&anc[u][i]!=anc[v][i]) cal(u,i),cal(v,i);
    cal2(cost[u]),cal2(cost[v]);
}

int main(){
    //freopen("a.txt","r",stdin);
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&node[i].u,&node[i].v,&node[i].w);
        sort(node+1,node+m+1);
        kruscal();
        dfs(1,-1,0);//转化为有根树
        process();//倍增法预处理x的第i个祖先及路径上的最长边和次长边
        int ans=INF;
        for(int i=1;i<=m;i++){
            int u=node[i].u,v=node[i].v,w=node[i].w,tmp;
            max1=0,max2=0;
            if(!vis[i]){
                query(u,v);//求MST上任意两点的最长边和严格次长边
                if(max1==w) tmp=mst+w-max2;
                else tmp=mst+w-max1;
                ans=min(ans,tmp);
            }
        }
        printf("%d\n",ans);
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值