[动态MST] [CDQ分治] BZOJ2001: [Hnoi2010]City 城市建设

题意

PS国是一个拥有诸多城市的大国,国王Louis为城市的交通建设可谓绞尽脑汁。Louis可以在某些城市之间修建道路,在不同的城市之间修建道路需要不同的花费。Louis希望建造最少的道路使得国内所有的城市连通。但是由于某些因素,城市之间修建道路需要的花费会随着时间而改变,Louis会不断得到某道路的修建代价改变的消息,他希望每得到一条消息后能立即知道使城市连通的最小花费总和, Louis决定求助于你来完成这个任务。

题解

这个题目是每次修改边权,需要你马上求出全局的MST。
改变一条边后对MST的影响是很大的,显然要往CDQ分治的方面想。
一般的CDQ分治都是考虑左边的修改对右边询问的贡献,但是这道题的这个贡献很难简单表达。
所以这里用了一个很特殊的思路:分治时不断重建图使得图的规模变小,保证复杂度。
大概就是:

void Solve(L,R){
    if(L==R){ 直接计算ans[L]; return; }
    干一些奥妙重重的事情缩图;
    Solve(L,mid); Solve(mid+1,R);
}

为了方便表述,对于 [L,R] 所有操作修改到的边,我们称作修改边,修改边的集合为 S |S|RL+1
缩图有这样两个核心操作:
Reduction:
把修改边的边权暂且记为 INF ,对全图刷 MST ,不在这个 MST 中的边,不管之后修改的边权为多少,一定是没用的,直接扔掉。
显然用这个操作可以使边数不超过 |S|+n1
Contraction:
把修改边的边权暂且记为 INF ,对全图刷 MST ,在这个 MST 中的非修改边,不管之后修改的边权为多少,都是必须要选的。
把这些边连上后,把一块连通的点缩在一起。
这个操作可以使点数不超过 n((n1)|S|)=|S|+1

上述两个操作的复杂度都为 O(mlogm)
所以我们执行 ContractionReduction ,图的规模就从 (n,m) 变成 (|S|+1,2|S|) 了!
然后就好了,复杂度为 O(mlog2m)

有点难写,于是参考了网上很多dalao的代码,后来改来改去都写的几乎一样了…

#include<cstdio>
#include<algorithm>
#define Fir first 
#define Sec second 
using namespace std;
typedef long long LL;
const int maxn=20005,maxq=50005,maxe=50005;
int w[maxe];
LL ans[maxq];
struct Edge{
    int x,y,w,id;
    bool operator < (const Edge &B)const{
        return w<B.w;
    }
} e[25][maxe],tmp[maxe],t_e[maxe];
pair<int,int> q[maxq];
int fa[maxn],t_id[maxn];
bool vis[maxn];
int getfa(int x){ fa[x]=fa[x]==x?x:fa[x]=getfa(fa[x]); }
void merge(int x,int y){
    x=getfa(x); y=getfa(y);
    if(x!=y) fa[x]=y;
}
void Contraction(int &_n,int &_m,LL &res){
    int n_now=0,m_now=0;
    for(int i=1;i<=_n;i++) fa[i]=i; 
    sort(tmp+1,tmp+1+_m);
    for(int i=1;i<=_m;i++){
        if(getfa(tmp[i].x)!=getfa(tmp[i].y))
            merge(tmp[i].x,tmp[i].y), t_e[++m_now]=tmp[i];
    }
    for(int i=1;i<=_n;i++) fa[i]=i;
    for(int i=1;i<=m_now;i++) 
     if(t_e[i].w!=-1e+9&&getfa(t_e[i].x)!=getfa(t_e[i].y))
      merge(t_e[i].x,t_e[i].y), res+=t_e[i].w;
    for(int i=1;i<=_n;i++) vis[i]=false;
    for(int i=1;i<=_n;i++) if(!vis[getfa(i)]) vis[getfa(i)]=true, t_id[getfa(i)]=++n_now;
    m_now=0;
    for(int i=1;i<=_m;i++)
        if(getfa(tmp[i].x)!=getfa(tmp[i].y)){
            t_e[++m_now]=tmp[i];
            t_e[m_now].x=t_id[getfa(tmp[i].x)];
            t_e[m_now].y=t_id[getfa(tmp[i].y)];
        }
    for(int i=1;i<=m_now;i++) tmp[i]=t_e[i];
    _n=n_now; _m=m_now;
}
void Reduction(int _n,int &_m){
    for(int i=1;i<=_n;i++) fa[i]=i; 
    sort(tmp+1,tmp+1+_m);
    int m_now=0;
    for(int i=1;i<=_m;i++){
        if(getfa(tmp[i].x)!=getfa(tmp[i].y)){
            merge(tmp[i].x,tmp[i].y);
            t_e[++m_now]=tmp[i];
        } else if(tmp[i].w==1e+9) t_e[++m_now]=tmp[i];
    }
    for(int i=1;i<=m_now;i++) tmp[i]=t_e[i];
    _m=m_now;
}
int pos[maxe];
void Solve(int L,int R,int k,int _n,int _m,LL res){
    if(L==R) w[q[L].Fir]=q[L].Sec;
    for(int i=1;i<=_m;i++) e[k][i].w=w[e[k][i].id];
    for(int i=1;i<=_m;i++) tmp[i]=e[k][i];
    if(L==R){
        for(int i=1;i<=_n;i++) fa[i]=i;
        sort(tmp+1,tmp+1+_m);
        for(int i=1;i<=_m;i++) if(getfa(tmp[i].x)!=getfa(tmp[i].y)){
            merge(tmp[i].x,tmp[i].y); res+=tmp[i].w;
        }
        ans[L]=res;
        return;
    }
    for(int i=1;i<=_m;i++) pos[tmp[i].id]=i;
    for(int i=L;i<=R;i++) tmp[pos[q[i].Fir]].w=-1e+9;
    Contraction(_n,_m,res);
    for(int i=1;i<=_m;i++) pos[tmp[i].id]=i;
    for(int i=L;i<=R;i++) tmp[pos[q[i].Fir]].w=1e+9;
    Reduction(_n,_m);
    for(int i=1;i<=_m;i++) e[k+1][i]=tmp[i]; 
    int mid=(L+R)>>1;
    Solve(L,mid,k+1,_n,_m,res); Solve(mid+1,R,k+1,_n,_m,res);
}
int n,m,Q;
int main(){
    freopen("bzoj2001.in","r",stdin);
    freopen("bzoj2001.out","w",stdout);
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&e[0][i].x,&e[0][i].y,&e[0][i].w);
        w[e[0][i].id=i]=e[0][i].w;
    }
    for(int i=1;i<=Q;i++) scanf("%d%d",&q[i].Fir,&q[i].Sec);
    Solve(1,Q,0,n,m,0);
    for(int i=1;i<=Q;i++) printf("%lld\n",ans[i]);
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值