17AHU排位赛2 A题(最小生成树、LCA维护树上路径)

problem

有一个n个点m条边的连通无向图(无重边、无自环),点的编号为1~n。每条边都有一个正的边权,并且每条边边权互不相同(即数据保证该图的最小生成树唯一)。
如果只是求最小生成树,那么Ohyee觉得太简单了,于是他决定考考你。
对于每条边(u,v)都进行询问:
——如果该边在最小生成树上,那么输出“Ohyee”;
——如果该边不在最小生成树上,那么输出最小生成树上u,v两点路径中边权的最小值。

Input

第一行输入两个整数n,m(2<=n<=100000,n-1<=m<=200000)
接下来m行,每行输入三个整数u,v,w。u,v表示该边连接点的编号,w是边权。
(1<=w<=1000000000)

Output

输出一共m行,按照输入的边的顺序回答每条边的询问。

Input

4 4
1 2 2
2 3 5
3 4 7
4 1 3

Output

Ohyee
Ohyee
2
Ohyee

Limitation

1s 256MB

Hint

原图最小生成树中的边是(1,2,2) (2,3,5) (4,1,3)
对于输入的第三条边(3,4,7),树上3->4的路径是3->2->1->4,边权分别是5,2,3,最小值是2。


思路

最小生成树和树上路径信息维护的结合,两个模板套在一起即可


代码示例

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+50;
const int maxm=2e5+50;
const int inf=0x3fffffff;

int n,m;//n为点数 m为边数

struct eee{//解决lca
    int from,to,dist;
    eee(int u,int v,int w):from(u),to(v),dist(w){
    }
};

vector<eee> edges;//边的具体信息
vector<int> GG[maxn];//边的编号

void addEdge(int u,int v,int w){//vector邻接表
    edges.push_back(eee(u,v,w));
    edges.push_back(eee(v,u,w));
    int size=edges.size();
    GG[u].push_back(size-2);
    GG[v].push_back(size-1);
}

const int maxlog=30;
int grand[maxn][maxlog];
int gmax[maxn][maxlog];
int depth[maxn];
int s;//倍增最大步数
int root;//根节点

void dfs(int x)//预处理
{
    for(int i=1;i<=s;++i){
        grand[x][i]=grand[grand[x][i-1]][i-1];
        gmax[x][i]=max(gmax[x][i-1],gmax[grand[x][i-1]][i-1]);
        if(!grand[x][i]) break;
    }
    for(int i=0;i<GG[x].size();i++){
        eee & e=edges[GG[x][i]];
        if(e.to!=grand[x][0]){
            depth[e.to]=depth[x]+1;
            grand[e.to][0]=x;
            gmax[e.to][0]=e.dist;
            dfs(e.to);
        }
    }
}

void init()
{
    s=floor(log(n+0.0)/log(2.0));
    depth[0]=-1;
    //memset(depth,0,sizeof(depth));
    memset(grand,0,sizeof(grand));
    memset(gmax,0,sizeof(gmax));
    root=1;
    dfs(root);//以1为根结点建树
}

int lca(int a,int b,int &maxx)//最大值
{
    if(depth[a]>depth[b]) swap(a,b);
    maxx=gmax[b][0];//之前的bug,应该放更深的b

    int dre=depth[b]-depth[a];
    for(int i=s;i>=0;--i){
        if(dre&(1<<i)) maxx=max(maxx,gmax[b][i]),b=grand[b][i];
    }
    if(a==b) return a;
    for(int i=s;i>=0;i--)
        if(grand[a][i]!=grand[b][i]){
            maxx=max(maxx,gmax[a][i]),maxx=max(maxx,gmax[b][i]);
            a=grand[a][i],b=grand[b][i];
        }
    maxx=max(maxx,gmax[a][0]);
    maxx=max(maxx,gmax[b][0]);
    return grand[a][0];
}

typedef struct{//边集数组
    int beg;
    int endd;
    int weight;
    int flag;//1表示在 0表示不在
    int order;//进来时的顺序
}Edge;


bool cmp(Edge a,Edge b)//边集数组排序
{
    return a.weight<b.weight;
}

bool cmp1(Edge a,Edge b)//原位置排序
{
    return a.order<b.order;
}

typedef struct{
    Edge xiang[maxm];//边的信息
    int numVertexes,numEdges;//顶点数和边数
}MGraph;

MGraph G;

void CreateMGraph()
{
    for(int i=0;i<m;++i){
        cin>>G.xiang[i].beg>>G.xiang[i].endd>>G.xiang[i].weight;
        G.xiang[i].order=i;
    }
    G.numEdges=m;
    G.numVertexes=n;
    sort(G.xiang,G.xiang+m,cmp);
}

int parent[maxm];//辅助并查集

int Find(int f)//查找连线顶点的尾部下标
{
    return parent[f]!=f?parent[f]=Find(parent[f]):f;
}

void MiniSpanTree_Kruskal()
{
    int nn,mm;
    //int parent[maxm];//定义一数组用来判断边与边是否形成环路
    for(int i=0;i<G.numVertexes;++i)
        parent[i]=i;//初始化数组值为i
    for(int i=0;i<G.numEdges;++i){//循环每一条边
        nn=Find(G.xiang[i].beg);
        mm=Find(G.xiang[i].endd);
        if(nn!=mm){//假如n与m不等,说明此边没有与现有生成树形成环路
            parent[nn]=mm;//将此边的结尾顶点放入下标为起点的parent中
            //表示此顶点已经在生成树集合中
            //printf("在:(%d,%d) %d\n",G.xiang[i].beg,G.xiang[i].endd,G.xiang[i].weight);
            //sum+=G.xiang[i].weight;
            G.xiang[i].flag=1;
            addEdge(G.xiang[i].beg,G.xiang[i].endd,1000000001-G.xiang[i].weight);//进入lca,注意求最小值,所以拿大值减后求最大值
            //cout<<G.xiang[i].beg<<' '<<G.xiang[i].endd<<' '<<1000000001-G.xiang[i].weight<<endl;
        }
        else{
            G.xiang[i].flag=0;//不在
        }
        //else printf("不在:(%d,%d) %d\n",G.xiang[i].beg,G.xiang[i].endd,G.xiang[i].weight);
    }
    //cout<<sum<<endl;//路径和
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    CreateMGraph();
    //for(int i=0;i<G.numEdges;++i) cout<<G.xiang[i].beg<<" "<<G.xiang[i].endd<<" "<<G.xiang[i].weight<<endl;
    //cout<<G.numEdges<<G.numVertexes<<endl;
    MiniSpanTree_Kruskal();
    //for(int i=0;i<G.numVertexes;++i)
    //cout<<parent[i]<<' ';
    sort(G.xiang,G.xiang+m,cmp1);
    init();
    int maxx;//生成树上两点间最短边权
    for(int i=0;i<m;++i){
        if(G.xiang[i].flag==1){
            cout<<"Ohyee"<<endl;
        }
        else{
            lca(G.xiang[i].beg,G.xiang[i].endd,maxx);
            //cout<<lca(u,v,maxx,sum)<<endl;
            cout<<1000000001-maxx<<endl;//再减去即为最小值
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值