HNOI2010 城市建设

很妙的题目。

首先可以将询问给离线下来,将一条边的边权更改变为在 [l,r] 的时间段中的权值为 w,所以考虑线段树分治。

然后我们发现要求的就是每个时间的最小生成树,那我们如何优化复杂度呢?

我们有一个容易想到的方法:

  1. 对于时间段 [l,r],设这段时间中未被更改的边集为 E,那么若只对 E 中的边做最小生成树(kruskal),如果边 (ui,vi,wi) 未被加入到树边当中,那么这条边一定不会被选择;

  2. 如果让其他不在 E 中的边(也就是被改动过的边)先进行 kruskal,然后再对 E 中的边尝试加入树边,那如果边 (ui,vi,wi) 被加入到最小生成树中,就说明这条边必然会被加入。然后把 E 中不属于这两种的边继续往下递归。

貌似这个做法很玄乎,但经过证明,我们发现这样做是对的,以下为证明:

操作二使得连通块个数不会超过 rl,而操作一则是删掉了没必要的边使边数连通块数与连通块数同级,故复杂度正确。

考虑使用线段树和可撤销并查集维护即可。

点击查看代码
#include<bits/stdc++.h>
#define fir first
#define sec second
#define int long long
#define lowbit(x) x&(-x)
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef pair<int,int> pir;
inline int read(){
int x=0,f=1; char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1; c=getchar();}
while(isdigit(c)){x=x*10+(c^48); c=getchar();}
return x*f;
}
const int inf=1e18,N=5e4+5;
int n,m,q;
struct dsu{int a,b,val;}st[N];
int fa[N],siz[N],top,res;
inline int getfa(int x){
if(fa[x]!=x) return getfa(fa[x]);
return fa[x];
}
inline bool merge(int u,int v,int w){
int a=getfa(u),b=getfa(v);
if(a==b) return 0;
if(siz[a]<siz[b]) swap(a,b);
siz[a]+=siz[b],fa[b]=a,res+=w;
st[++top]=dsu{a,b,w};
return 1;
}
inline void del(){
auto [a,b,val]=st[top];
siz[a]-=siz[b],fa[b]=b,res-=val;
top--;
}
struct edge{int u,v,w;}s[N];
inline bool cmp(edge a,edge b){return a.w<b.w;}
struct tree{
vector<edge> e[N<<2];
inline void add(int l,int r,int p,int ll,int rr,edge k){
if(ll>rr) return ;
if(ll<=l&&r<=rr){
e[p].push_back(k);
return ;
}
int mid=(l+r)>>1;
if(ll<=mid) add(l,mid,p<<1,ll,rr,k);
if(rr>mid) add(mid+1,r,p<<1|1,ll,rr,k);
}
inline void solve(int l,int r,int p){
int T=top;
sort(e[p].begin(),e[p].end(),cmp);
if(l==r){
for(auto [u,v,w]:e[p]) merge(u,v,w);
cout<<res<<'\n';
while(top>T) del();
assert(top==T);
return ;
}
int sz=e[p].size();
vector<int> vv;
vv.resize(sz);
for(int i=0;i<sz;i++) vv[i]=0;
for(int i=0;i<sz;i++){
auto [u,v,w]=e[p][i];
if(!merge(u,v,0)) vv[i]=1;
}
while(top>T) del();
for(int i=l;i<=r;i++){
auto [u,v,w]=s[i];
merge(u,v,0);
}
for(int i=0;i<sz;i++){
if(vv[i]) continue;
auto [u,v,w]=e[p][i];
if(merge(u,v,0)) vv[i]=2;
}
while(top>T) del();
for(int i=0;i<sz;i++){
auto [u,v,w]=e[p][i];
if(vv[i]==2) merge(u,v,w);
if(vv[i]==0){
e[p<<1].push_back(e[p][i]);
e[p<<1|1].push_back(e[p][i]);
}
}
int mid=(l+r)>>1;
solve(l,mid,p<<1);
solve(mid+1,r,p<<1|1);
while(top>T) del();
}
}Tr;
int lst[N];
int u[N],v[N],w[N];
signed main(){
n=read(),m=read(),q=read();
for(int i=1;i<=m;i++) u[i]=read(),v[i]=read(),w[i]=read(),lst[i]=1;
for(int i=1;i<=q;i++){
int id=read(),x=read();
if(i>1) Tr.add(1,q,1,lst[id],i-1,edge{u[id],v[id],w[id]});
lst[id]=i,w[id]=x;
s[i]=edge{u[id],v[id],w[id]};
}
for(int i=1;i<=m;i++) Tr.add(1,q,1,lst[i],q,edge{u[i],v[i],w[i]});
for(int i=1;i<=n;i++) siz[i]=1,fa[i]=i;
Tr.solve(1,q,1);
}
posted @   ~Cyan~  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示