题目描述
原题通道
sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好。为了方便起见,我们可以认为宇宙是一张有N 个顶点和M 条边的带权无向图,顶点表示各个星系,两个星系之间有边就表示两个星系之间可以直航,而边权则是航行的危险程度。
sideman 现在想把危险程度降到最小,具体地来说,就是对于若干个询问(A, B),sideman 想知道从顶点A 航行到顶点B 所经过的最危险的边的危险程度值最小可能是多少。作为sideman 的同学,你们要帮助sideman 返回家园,兼享受安全美妙的宇宙航行。所以这个任务就交给你了。
输入输出格式
输入格式:
第一行包含两个正整数N 和M,表示点数和边数。
之后 M 行,每行三个整数A,B 和L,表示顶点A 和B 之间有一条边长为L 的边。顶点从1 开始标号。
下面一行包含一个正整数 Q,表示询问的数目。
之后 Q 行,每行两个整数A 和B,表示询问A 和B 之间最危险的边危险程度的可能最小值。
输出格式:
对于每个询问, 在单独的一行内输出结果。如果两个顶点之间不可达, 输出impossible。
输入输出样例
输入样例#1
4 5
1 2 5
1 3 2
2 3 11
2 4 6
3 4 4
3
2 3
1 4
1 2
输出样例1:
5
4
5
说明
对于40% 的数据,满足N≤1000,M≤3000,Q≤1000。
对于 80% 的数据,满足N≤10000,M≤105,Q≤1000。
对于 100% 的数据,满足N≤105,M≤3×105,Q≤105,L≤109。数据不保证没有重边和自环。
题解:
这题其实就是
NOIP2013货车运输
的翻版,标签是倍增,但是我不想拘泥于倍增其实是不熟练就打了树剖。
可能因为这题有多个联通块,用树剖做起来比较麻烦(建n颗线段树)所以用树剖的人很少,但是这个麻烦还是可以解决的:
只要建一个虚点(这里使用0号节点),将所有联通块连起来即可,再从0号节点dfs就只用dfs一遍了,然后注意一些细节(在代码中指出),这题就基本上没什么问题了!
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define RG register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
inline int gi(){
int data=0,w=1;
char ch=0;
while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=data*10+ch-'0',ch=getchar();
return data*w;
}
#define N 100010
int n,m,fa[N],p[N],a[N];
struct Edge{int u,v,w;}e[N*3];//存最小生成树的边
struct Node{int to,cost;};
vector<Node> G[N];//本人习惯用vector存图
inline void Add_Edge(int u,int v,int w){
G[u].push_back((Node){v,w});
G[v].push_back((Node){u,w});
}
//并差集判是否在一个集合+最小生成树
int findSet(int x){return p[x]==x?x:p[x]=findSet(p[x]);}
inline void Unite(int u,int v){p[findSet(u)]=p[findSet(v)];}
inline bool same(int u,int v){return findSet(u)==findSet(v);}
inline bool cmp(const Edge &a,const Edge &b){return a.w<b.w;}
inline void kruskal(){
sort(&e[1],&e[m+1],cmp);
for(RG int i=1;i<=m;i++){
if(same(e[i].v,e[i].u)) continue;
Add_Edge(e[i].v,e[i].u,e[i].w);
Unite(e[i].v,e[i].u);
}
}
//以下是树链剖分
int top[N],dep[N],size[N],son[N],L[N],dfn[N],tim=-1;//细节1:tim赋为-1(因为从0开始dfs)
#define cur G[x][i]
void dfs(int x,int f){//把边权化为点权并求出fa[x]
fa[x]=f;
for(RG int i=0;i<G[x].size();i++){
if(cur.to==fa[x]) continue;
a[cur.to]=cur.cost;
dfs(cur.to,x);
}
}
void dfs1(int x){
dep[x]=dep[fa[x]]+1;
size[x]=1;
son[x]=n+1;//细节2:重儿子是赋为n+1(0是根节点)
for(RG int i=0;i<G[x].size();i++){
if(cur.to==fa[x]) continue;
dfs1(cur.to);
size[x]+=size[cur.to];
if(size[son[x]]<size[cur.to]) son[x]=cur.to;
}
}
void dfs2(int x,int tp){
top[x]=tp;
tim++;
dfn[tim]=x;L[x]=tim;
if(son[x]!=n+1) dfs2(son[x],tp);
for(RG int i=0;i<G[x].size();i++){
if(cur.to==fa[x]||cur.to==son[x]) continue;
dfs2(cur.to,cur.to);
}
}
//线段树维护路径最小
class SegMent_Tree{
#define lson ( o<<1)
#define rson ((o<<1)|1)
public:
int val[N<<2];
void build(int o,int l,int r){
if(l==r){
val[o]=a[dfn[l]];
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(o);
}
int query(int o,int ql,int qr,int l,int r){
if(l>qr||r<ql) return -99999999;
if(ql<=l&&r<=qr) return val[o];
int mid=(l+r)>>1;
return max(query(lson,ql,qr,l,mid),query(rson,ql,qr,mid+1,r));
}
inline int Ask(int u,int v){
int ret=-99999999;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ret=max(ret,query(1,L[top[u]],L[u],1,n));
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
return max(ret,query(1,L[v]+1,L[u],1,n));//细节3:注意是L[v]+1,自己想一想是什么原因
}
private:
inline void pushup(int o){val[o]=max(val[lson],val[rson]);}
}t;
int main(){
n=gi();m=gi();
for(RG int i=1;i<=m;i++) e[i].u=gi(),e[i].v=gi(),e[i].w=gi();
for(RG int i=1;i<=n;i++) p[i]=i;//初始化并查集
kruskal();//生成树
memset(fa,-1,sizeof(fa));//把fa数组初始化成-1,因为建的虚点为0
fa[0]=0;//细节4:fa[0]=0,要不然会RE
for(RG int i=1;i<=n;i++) if(fa[i]==-1) dfs(i,0);
for(RG int i=1;i<=n;i++) if(fa[i]==0) Add_Edge(i,0,0);
dfs1(0);dfs2(0,0);
RG int T=gi();
t.build(1,1,n);
while(T--){
int u=gi(),v=gi();
if(!same(u,v)) puts("impossible");
else printf("%d\n",t.Ask(u,v));
}
return 0;
}