HNOI2010 城市建设

很妙的题目。

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

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

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

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

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

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

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

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

点击查看代码
#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 @ 2024-08-29 22:14  ~Cyan~  阅读(29)  评论(0)    收藏  举报