Solution
题解里都是暴力的做法,这里也介绍一下。
既然要求找最小的边,那么必然要将 ((l,r)) 的边从大到小排序,然后将每条边的两个端点尽量分到不同的集合,直到分不了,就可以输出答案了。
时间复杂度:(O(mq))
要不是时限 (6) 秒,就都得没了
但是想法是好的,我们可以沿着这个想法继续思考。
将要加入并查集的边分为三种:
- (u) 和 (v) 不在同一个连通块里,那么将连通块联通,并标记 (u,v) 不在同一集合
- (u) 和 (v) 在同一连通块中,但是不在同一集合,那么跳过即可
- (u) 和 (v) 在同一连通块中,但是在一个集合里,输出这条边
我们发现第 (1) 种边最多有 (n-1) 条,第 (3) 种边有 (1) 条,并且最后的答案和第 (2) 种边是没有关系的。
也就是说第 (1) 种的这 (n-1) 边就足以确定那个并查集了。
那么将这 (n-1) 条边和第 (3) 种边,即总共 (n) 条边存下来最后用来判断答案,就可以将复杂度 (O(m) ightarrow O(n)) 。
用什么来存储呢?
我们发现还有一个细节没说——它是对于一个区间来询问的
哦,那就用线段树存吧(*▽*)
但是问题是在建树时怎么向上pushup呢?
我们需要维护每个区间最多不超过 (n) 条边而且边还要从大到小排序。排序好说,在合并两个区间的边时,归并排序即可;那么当两个区间合并时,怎么判断剩下的是哪几条边呢?还是拿并查集来维护嘛╮(╯-╰)╭
查询的时候同理。
建树复杂度为 (O[nmalpha(n)]) ,查询复杂度为 (O[qnalpha(n)log m]) ,总时间复杂度为 (O[alpha(n)n(m+qlog m)]) 。
Code
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls rt<<1
#define rs rt<<1|1
#define VI vector<int>
using namespace std;
const int N=1010,M=500010;
int n,m,q,X[M],Y[M],W[M],f[N],g[N],p[M];
VI tr[M<<2];
VI::iterator ia,ib;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
int find(int x){
if(f[x]==x) return x;
int t=f[x];
f[x]=find(t);
g[x]^=g[t];
return f[x];
}
inline VI merge(VI a,VI b){
int cnt=0;
VI res;
for(ia=a.begin();ia!=a.end();ia++)
f[X[*ia]]=X[*ia],f[Y[*ia]]=Y[*ia],g[X[*ia]]=g[Y[*ia]]=0;
for(ia=b.begin();ia!=b.end();ia++)
f[X[*ia]]=X[*ia],f[Y[*ia]]=Y[*ia],g[X[*ia]]=g[Y[*ia]]=0;
for(ia=a.begin(),ib=b.begin();ia!=a.end()||ib!=b.end();){
if(ia!=a.end()&&(ib==b.end()||W[*ia]>W[*ib]))
p[++cnt]=*ia,ia++;
else p[++cnt]=*ib,ib++; //归并排序思想
}
for(int i=1,x,y;i<=cnt;i++){
x=X[p[i]],y=Y[p[i]];
if(find(x)!=find(y)) g[find(x)]=g[x]^g[y]^1,f[f[x]]=f[y],res.push_back(p[i]);
else if(g[x]!=g[y]) continue;
else{
res.push_back(p[i]);
break;
}
}
return res;
}
void build(int rt,int l,int r){
if(l==r){
tr[rt].push_back(l);
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid); build(rs,mid+1,r);
tr[rt]=merge(tr[ls],tr[rs]);
}
VI query(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R) return tr[rt];
int mid=(l+r)>>1;
if(R<=mid) return query(ls,l,mid,L,R);
if(L>mid) return query(rs,mid+1,r,L,R);
return merge(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
}
int main(){
VI t;
n=read(); m=read(); q=read();
for(int i=1;i<=m;i++)
X[i]=read(),Y[i]=read(),W[i]=read();
build(1,1,m);
for(int i=1,l,r,x,y;i<=q;i++){
l=read(),r=read();
t=query(1,1,m,l,r);
for(ia=t.begin();ia!=t.end();ia++) f[X[*ia]]=X[*ia],f[Y[*ia]]=Y[*ia];
for(ia=t.begin();ia!=t.end();ia++){
x=X[*ia],y=Y[*ia];
if(find(x)==find(y)) break;
f[find(x)]=find(y);
}
if(ia==t.end()) puts("-1");
else printf("%d
",W[*ia]);
}
return 0;
}