CF687D Dividing Kingdom II
Solution
题解里都是暴力的做法,这里也介绍一下。
既然要求找最小的边,那么必然要将 的边从大到小排序,然后将每条边的两个端点尽量分到不同的集合,直到分不了,就可以输出答案了。
时间复杂度:
要不是时限 秒,就都得没了
但是想法是好的,我们可以沿着这个想法继续思考。
将要加入并查集的边分为三种:
- 和 不在同一个连通块里,那么将连通块联通,并标记 不在同一集合
- 和 在同一连通块中,但是不在同一集合,那么跳过即可
- 和 在同一连通块中,但是在一个集合里,输出这条边
我们发现第 种边最多有 条,第 种边有 条,并且最后的答案和第 种边是没有关系的。
也就是说第 种的这 边就足以确定那个并查集了。
那么将这 条边和第 种边,即总共 条边存下来最后用来判断答案,就可以将复杂度 。
用什么来存储呢?
我们发现还有一个细节没说——它是对于一个区间来询问的
哦,那就用线段树存吧(*▽*)
但是问题是在建树时怎么向上pushup呢?
我们需要维护每个区间最多不超过 条边而且边还要从大到小排序。排序好说,在合并两个区间的边时,归并排序即可;那么当两个区间合并时,怎么判断剩下的是哪几条边呢?还是拿并查集来维护嘛╮(╯-╰)╭
查询的时候同理。
建树复杂度为 ,查询复杂度为 ,总时间复杂度为 。
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\n",W[*ia]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】