[bzoj2001][Hnoi2010]City 城市建设——动态最小生成树+分治

题目大意:

给定一个图,每次修改一条边的边权,每次求最小生成树。

思路:

神仙题目。
虽然代码的形式就是分治,但是和普通的分治还是有区别的。
考虑kruskal是怎么求最小生成树的,即暴力排序之后利用并查集维护连通信息。
所以这里的最裸的暴力就是每一次修改一条边的边权之后重新排序,再重新用并查集维护信息。
这题的一切都是从上面的这个想法出发的,所以现在考虑如何优化上述算法。
不难发现对于一段连续的修改,其中有大部分的信息是不变的,同时,不变的信息也随着修改的区间的长度的增长而减少。
对于一段[l,r]的修改操作,假设原来的图的规模为n,如果我们能够每次把最小生成树的公共信息提取出来,即哪些边一定会再最小生成树上面,哪些边一定不会在最小生成树上面,这样以后图的规模会缩小到(r-l+1)级别,如果我们要处理这一区间内的询问,只需要对这个(r-l+1)规模的图进行操作。
于是一个利用重复的信息来减少复杂度的算法就可以得到了,即不断地将询问分成若干部分,假设分成了d部分,那么会将图分成了q/d个不同时间段图,每一个图的规模大小为n/d,同时得到了q/d个子问题。
现在考虑如何求解一定在MST上的边,即将所有的不确定边设为-inf之后仍在MST上的边。
至于一定不在MST上的边,即将所有的不确定边设为inf后仍不在MST上的边。
所以缩减图的规模其实十分耗时,所以这里把d设为了2。(其实这里我也不知道为什么,并不知道怎么分析时间复杂度)。

#include<bits/stdc++.h>

#define REP(i,a,b) for(register int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(register int i=a;i##_end_=b;i>=i##_end_;--i)
#define pii pair<int,ll>
#define fi first
#define se second
#define mk make_pair
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj2001.in","r",stdin);
    freopen("bzoj2001.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='0')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=2e4+10;
const int maxm=5e4+10;
int n,m,q; 
pii qu[maxm];
int tot[22],bel[22][maxn],s[22][maxm],t[maxm];
bool in[maxm];
int fa[maxn];
ll ans[maxm];

struct node{
    int u,v,ty;
    ll w;
}e[maxm];

bool cmp(int x,int y){
    if(e[x].ty!=e[y].ty)return e[x].ty<e[y].ty;
    return e[x].w<e[y].w;
}

int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);}

ll kruskal(int siz,int dep){
    ll ret=0;
    sort(t+1,t+siz+1,cmp);
    REP(i,1,siz){
        int u=e[t[i]].u,v=e[t[i]].v;
        u=bel[dep][u]; v=bel[dep][v];
        fa[u]=u; fa[v]=v; in[t[i]]=0;
    }
    REP(i,1,siz){
        int u=e[t[i]].u,v=e[t[i]].v;
        u=bel[dep][u]; v=bel[dep][v];
        if(find(u)==find(v))continue;
        fa[find(u)]=find(v);
        ret+=e[t[i]].w;
        in[t[i]]=1;
    }
    return ret;
}

ll merge(int l,int r,int dep){
    ll ret=0;
    REP(i,1,tot[dep])t[i]=s[dep][i];
    REP(i,l,r)e[qu[i].fi].ty=-1;
    kruskal(tot[dep],dep-1);

    REP(i,1,tot[dep]){
        int u=bel[dep-1][e[t[i]].u],v=bel[dep-1][e[t[i]].v];
        fa[u]=u; fa[v]=v;
    }

    REP(i,1,tot[dep]){
        if(!in[t[i]] || e[t[i]].ty==-1)continue;
        int u=bel[dep-1][e[t[i]].u],v=bel[dep-1][e[t[i]].v];
        ret+=e[t[i]].w;
        fa[find(u)]=find(v);
    }

    REP(i,1,tot[dep]){
        int u=e[t[i]].u,v=e[t[i]].v;
        int uu=bel[dep-1][u],vv=bel[dep-1][v];
        bel[dep][u]=find(uu); bel[dep][v]=find(vv);
    }
    int sz=0;
    REP(i,1,tot[dep]){
        if(in[t[i]] && e[t[i]].ty!=-1)continue;
        s[dep][++sz]=t[i];
    }
    tot[dep]=sz;
    REP(i,l,r)e[qu[i].fi].ty=0;
    return ret;
}

void erase(int l,int r,int dep){
    REP(i,1,tot[dep])t[i]=s[dep][i];
    REP(i,l,r)e[qu[i].fi].ty=1;
    kruskal(tot[dep],dep-1);

    int sz=0;
    REP(i,1,tot[dep]){
        if(!in[t[i]] && e[t[i]].ty!=1)continue;
        s[dep][++sz]=t[i];
    }
    tot[dep]=sz;
    REP(i,l,r)e[qu[i].fi].ty=0;
}

void divide(int l,int r,int dep,ll sum){
    tot[dep]=tot[dep-1];
    REP(i,1,tot[dep]){
        s[dep][i]=s[dep-1][i];
        int u=e[s[dep][i]].u,v=e[s[dep][i]].v;
        bel[dep][u]=bel[dep-1][u];
        bel[dep][v]=bel[dep-1][v];
    }
    ll val=merge(l,r,dep);
    erase(l,r,dep);
    if(l==r){
        e[qu[l].fi].w=qu[l].se;
        REP(i,1,tot[dep])t[i]=s[dep][i];
        ans[l]=kruskal(tot[dep],dep)+sum+val;
        return;
    }
    int mid=(l+r)>>1;
    divide(l,mid,dep+1,sum+val);
    divide(mid+1,r,dep+1,sum+val);
}

void init(){
    read(n); read(m); read(q);
    REP(i,1,m)read(e[i].u),read(e[i].v),read(e[i].w);
    REP(i,1,q)read(qu[i].fi),read(qu[i].se);
    tot[0]=m;
    REP(i,1,n)bel[0][i]=i;
    REP(i,1,m)s[0][i]=i;
}

int main(){
    File();
    init();
    divide(1,q,1,0);
    REP(i,1,q)printf("%lld\n",ans[i]);
    //cerr<<(double)clock()/CLOCKS_PER_SEC<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值