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;
}