[BZOJ2001][HNOI2010]City城市建设-CDQ分治

City 城市建设

Description

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

Input

文件第一行包含三个整数N,M,Q,分别表示城市的数目,可以修建的道路个数,及收到的消息个数。
接下来M行,第i+1行有三个用空格隔开的整数Xi,Yi,Zi(1≤Xi,Yi≤n, 0≤Zi≤50000000),表示在城市Xi与城市Yi之间修建道路的代价为Zi。
接下来Q行,每行包含两个数k,d,表示输入的第k个道路的修建代价修改为d(即将Zk修改为d)。

Output

输出包含Q行,第i行输出得知前i条消息后使城市连通的最小花费总和。

Sample Input

5 5 3
1 2 1
2 3 2
3 4 3
4 5 4
5 1 5
1 6
1 1
5 3

Sample Output

14
10
9

HINT

【数据规模】
对于20%的数据, n≤1000,m≤6000,Q≤6000。
有20%的数据,n≤1000,m≤50000,Q≤8000,修改后的代价不会比之前的代价低。
对于100%的数据, n≤20000,m≤50000,Q≤50000。


啊咱为什么要做这道题……
感觉调到最后意识模糊完全是在对着标程改……

(╯‵□′)╯︵┻━┻


思路:
首先,考虑最小生成树的性质。
本质上,最小生成树是个贪心(咱在知乎上看到过到底是贪心还是DP的讨论)。
那么每一时刻它都将会选择当前局部最优解。

那么,从prim的角度思考:
如果咱把一些点看成一个点集S。
考虑对整个图做最小生成树T。
那么所有不在T上的、在S的补集内部连接的所有边都将毫无意义。
因为这样的一条边显然在T中具有更优解,即使S变化改变了T,也改变不了有边比它优,它毫无意义的事实。
那么,删掉这些边~

同时,从kruskal的角度,考虑T中的边。
可以发现,T中至少有一个端点位于S的补集中的所有边一定会出现在最后的最小生成树中。
因为,S集合无论怎么乱变,带来的后果只是加入或删除刚改变的这条边,并需要删除或加入一条边。原来使S的补集中的点加入最小生成树的边是不会因此改变的,即使这条边连接了S和S的补集也一样。
换句话说,对T中至少有一个端点位于S的补集中的边在最终结果中的的存在性完全不构成影响。
那么就可以考虑缩掉T上的这些不属于S集合的点了,因为它们已经被确定了~
缩成一个点就好~

于是,考虑分治询问,每次把与分治区间内修改的边相关的所有点看做S,递归下去,同步缩点即可~

#include<iostream>
#include<map>
#include<set>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

typedef long long ll;

inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}
const int N=20009;
const int M=50009;
const int Q=50009;

struct query
{
    int u,w;
}op[Q];

struct edge
{
    int u,v,w,id;
    bool del,un;
    edge(){del=un=0;}
};

inline bool operator < (edge a,edge b){return a.w<b.w;}

vector<edge> e;
ll ans[Q];
int n,m,q,fa[N];

inline int find(int x)
{
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}

inline void reset(int siz)
{
    for(int i=1;i<=siz;i++)
        fa[i]=i;
}   

inline bool merge(int u,int v)
{
    int fu=find(u),fv=find(v);
    if(fv==fu)return 0;
    fa[fv]=fu;return 1;
}

inline void solve(int l,int r,vector<edge> e,int siz,ll sum,int addl,int addr)
{
    map<int,int> ha;
    int mid=l+r>>1,ei=e.size();
    if(l==r)addr=r;

    for(int i=0;i<ei;i++)//get edges' original name
        ha[e[i].id]=i;

    for(int i=addl;i<=addr;i++)//preadd edge
        if(ha.count(op[i].u))
            e[ha[op[i].u]].w=op[i].w;

    sort(e.begin(),e.end());//sort for MST

    if(l==r)//getans
    {
        reset(siz+9);
        for(int i=0;i<ei;i++)
            if(merge(e[i].u,e[i].v))
                sum+=e[i].w;
        ans[l]=sum;
        return;
    }

    set<int> need;
    map<int,int> hath;
    reset(siz+9);

    for(int i=l;i<=r;i++)//get need edges
        need.insert(op[i].u);

    for(int i=0;i<ei;i++)
        if(!need.count(e[i].id) && !merge(e[i].u,e[i].v))//ignore need edges,use the rest to build MST
            e[i].del=1;

    reset(siz+9);
    for(int i=0;i<ei;i++)
        if(need.count(e[i].id))//ignore all edges which was in pointset S
            merge(e[i].u,e[i].v);

    for(int i=0;i<ei;i++)
        if(!need.count(e[i].id) && !e[i].del && merge(e[i].u,e[i].v))//calc edges which must be in MST
            e[i].un=1,sum+=e[i].w;

    reset(siz+9);
    for(int i=0;i<ei;i++)//merge points with edges which must be in MST to ignore them
        if(e[i].un)
            merge(e[i].u,e[i].v);

    int top=0;
    for(int i=1;i<=siz;i++)//get points which were not added into MST yet
        if(find(i)==i)
            hath[i]=++top;

    vector<edge> ne;
    for(int i=0;i<ei;i++)
        if(!e[i].un && !e[i].del)//ignore must-in and mustn't-in edges
        {
            edge tmp=e[i];
            tmp.u=hath[find(e[i].u)];
            tmp.v=hath[find(e[i].v)];
            ne.push_back(tmp);
        }

    solve(l,mid,ne,top,sum,l,0);
    solve(mid+1,r,ne,top,sum,l,mid);//preadd l-mid optmize
}

int main()
{
    n=read();m=read();q=read();
    for(int i=1;i<=m;i++)
    {
        edge t;
        t.u=read();
        t.v=read();
        t.w=read();
        t.id=i;
        e.push_back(t);
    }

    for(int i=1;i<=q;i++)
    {
        op[i].u=read();
        op[i].w=read();
    }

    solve(1,q,e,n,0,1,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、付费专栏及课程。

余额充值