HNOI2010 城市建設

~Cyan~發表於2024-08-29

很妙的題目。

首先可以將詢問給離線下來,將一條邊的邊權更改變為在 \([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);
}

相關文章