[HNOI2010] 城市建设 题解
题意
给定一个图,每次修改一条边的边权,每次修改后输出该图的最小生成树边权和,询问间不独立。
思路
在线不好做,考虑离线下来,可以使用线段树分治。
我们称在当前区间 \(\left[ l,r\right]\) 的边为动态边,不在的边为静态边。
但这样每次遍历到叶子节点的时候静态边的数量都是 \(m\) 的,无法接受。
考虑在递归的过程中删去一些无用的静态边。先将所有动态边连上,然后跑一遍最小生成树,此时树上的所有静态边在接下来的子区间一定会选上,可以提前把其贡献加入答案中然后删去。此时再将连上的动态边给撤销回去,注意到未被删去的静态边中有些因树上的静态边连上了而导致无用,可以删去这些边。
遍历到叶子的时候直接暴力跑最小生成树即可。
复杂度证明,在第一次删边中把点数控制到区间长度级别,第二次删边中把边数控制为点数,所以边数是区间长度级别,所以分治复杂度带一个 \(\log\),因为要并查集维护和排序,所以总体复杂度是 \(O(n \log^2 n)\)。
代码
点击查看代码
#include<bits/stdc++.h>
#define rep(i,a,n) for(int i=(a);i<=(n);++i)
#define per(i,a,n) for(int i=(n);i>=(a);--i)
#define SZ(x) ((int)(x).size())
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=2e5+10;
int n,m,q;
struct node{
int u,v,w;
bool operator < (const node &A)const{
return w<A.w;
}
}e[maxn];
struct dsu{
int fa[maxn],sz[maxn],n;
stack<pii> stk;
inline void init(int x){
n=x;
rep(i,1,n) fa[i]=i,sz[i]=1;
}
inline int find(int x){
while(x^fa[x]) x=fa[x];
return x;
}
inline bool merge(int id){
int u=e[id].u,v=e[id].v;
u=find(u); v=find(v);
if(u^v){
if(sz[u]>sz[v]) swap(u,v);
fa[u]=v;
sz[v]+=sz[u];
stk.push({u,v});
return true;
}
return false;
}
inline void reback(int o){
while(SZ(stk)>o){
auto [u,v]=stk.top();
fa[u]=u; sz[v]-=sz[u];
stk.pop();
}
}
}T;
pii opt[maxn];
vector<int> mov,sta;
bool vis[maxn];
inline void divide(int l,int r,ll res){
vector<int> Mov=mov,Sta=sta,t,keep;
int p=SZ(T.stk);
rep(i,l,r) vis[opt[i].first]=true;
for(auto i:mov){
if(vis[i]) t.push_back(i);
else sta.push_back(i);
}
rep(i,l,r) vis[opt[i].first]=false;
mov=t;
for(auto i:mov) T.merge(i);
sort(sta.begin(),sta.end(),[&](int x,int y){return e[x]<e[y];});
t.clear();
for(auto i:sta){
if(T.merge(i)){
res+=e[i].w;
keep.push_back(i);
}
else t.push_back(i);
}
sta=t;
T.reback(p);
for(auto i:keep) T.merge(i);
int o=SZ(T.stk);
t.clear();
for(auto i:sta){
if(T.merge(i))
t.push_back(i);
}
sta=t;
T.reback(o);
if(l==r){
e[opt[l].first].w=opt[l].second;
sta.push_back(opt[l].first);
sort(sta.begin(),sta.end(),[&](int x,int y){return e[x]<e[y];});
for(auto i:sta){
if(T.merge(i))
res+=e[i].w;
}
printf("%lld\n",res);
}
else{
int mid=(l+r)>>1;
divide(l,mid,res); divide(mid+1,r,res);
}
mov=Mov; sta=Sta;
T.reback(p);
}
inline void solve(){
scanf("%d%d%d",&n,&m,&q);
rep(i,1,m) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w),mov.push_back(i);
rep(i,1,q) scanf("%d%d",&opt[i].first,&opt[i].second);
T.init(n);
divide(1,q,0);
}
signed main(){
int _=1;
//scanf("%d",&_);
while(_--) solve();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】