【HNOI2010】【BZOJ2001】City 城市建设2015.4.9编辑修改

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。
Source

Day2
写的第一个cdq的题竟然是个最小生成树+cdq我也真是醉了
自己最小生成树写的不好还硬要写
最后看了课件+别人题解才A
第一次看出来是分层图的意思但是不会构图于是学着别人vector、set、map乱搞
COGS和BZOJ过了(论BZOJ总时限的好处)
然而CODEVS无情的让我TLE了四个点(没办法乱用STL就是这个后果谁让我比较弱)
一开始向vector插入T的时候忘了初始化T的del和uni标记结果就是这个问题调了两个晚上卧槽(╯‵□′)╯︵┻━┻
用了很多最小生成树的定理
定理内容某课件里有提到
课件叫什么我忘了反正是个讲cdq分治的课件。网上有
修改部分:
原课件名:《cdq分治相关》
定理引用:

假设G是一个带权无向图的边集,S是G的一个子集。 将S中边权设为+inf后,T是图G的最小生成树。 也可以说T是G-S的MST

定理1:不管S中的边权怎么变化,G的最小生成树T’将属于T∪S。 对于任意一条不属于T∪S的边,我们可以在T中找到链接它两端的一条路径。
由于这些边取值都和S无关,无论S权值怎么更改,这个环上这条边最大,不会进入MST
定理2:在定理1的前提下,我们可以在不影响T’的情况下,将G的边数缩减到n+|s|-1以下。
直接运用定理1,G-S的最小生成树最多有N-1条边。
其他的边不可能在T’中,我们可以安全地删除掉。
这一步被称为Reduction,效果是减少了G的边数。
复杂度同MST是O(mlogm),m=|G|。
定理3:不管S中的边权怎么变化。G的最小生成树T’将包含T-S。
考虑将S的权值一条边一条边提升的情况。
每提升一条边权值,MST要么不变,要么就是S中的一条边离开,一条新边加入。
无论如何,T-S这些边都不会离开MST。
定理4:在定理3的前提下,我们可以在不影响T’的情况下,将G的点数缩减到|s|+1以下。
假设已经对图进行了Reduction。
根据定理3,由于T-S的边不离开MST,我们可以将这些边连接的点合并为联通分量,将联通分量视为节点。
之后根据节点归属更新边表即可。
这一步被称为Contraction,效果是减少了G的点数。
复杂度同MST,O(mlogm)
根据上面的推论,我们可以得到结论:
给定一个图G和操作序列S,可以在O(mlogm)时间内通过reduction-contraction将图的边数缩小到n+|s|-1,点数缩小到|s|+1而保持求解过程的正确性。

//AC code by CreationAugust
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#define MAXN 20010
#define MAXM 50010
#define MAXINT 0x7fffffff
using namespace std;
int n,m,q;
struct edge
{
    int u,v,w,num;
    bool del,uni;//是否被删边或合并点 
};
bool operator <(edge a,edge b)
{
    return a.w<b.w;
}
vector<edge> e;
struct modify
{
    int x,w;
}que[MAXM];
long long ans[MAXM];
int fa[MAXN];
int find(int a)
{
    return fa[a]==a?a:fa[a]=find(fa[a]);
}
bool union_n(int a,int b)
{
    int x=find(a),y=find(b);
    if (x==y) return false;
    fa[x]=y;return true;
}
void init(int x)
{
    for (int i=0;i<=x;i++) fa[i]=i;
}
void solve(int l,int r,vector<edge> e,int n,long long sum,int newl,int newr)
{
    map<int,int> hash;
    int mid=(l+r)>>1;
    if (l==r) newr=r;
    for (int i=0;i<e.size();i++) hash.insert(make_pair(e[i].num,i));
    for (int i=newl;i<=newr;i++) //修改边操作 
    {
        modify &q=que[i];
        if (hash.count(q.x)) 
            e[hash[q.x]].w=que[i].w;
    }
    sort(e.begin(),e.end());
    if (l==r)//找到答案 
    {
        init(n+10);//为了保险多初始化一点
        for (int i=0;i<e.size();i++) if (union_n(e[i].u,e[i].v)) sum+=e[i].w;
        ans[l]=sum;
        return; 
    }
    set<int> t;//仅仅需要使用l,r之间的边 
    for (int i=l;i<=r;i++) t.insert(que[i].x);
    init(n+10);
    for (int i=0;i<e.size();i++)//缩边,将询问中的边设置为INF.有定理这时总边集e中不在生成树里的边,一定不在l,r查询的生成树中 
    {
        if (t.count(e[i].num)) continue;//这一步相当于把该条边的边权定为无穷大,相当于删掉了该边 
        if (!union_n(e[i].u,e[i].v)) e[i].del=true;
    }
    init(n+10);
    for (int i=0;i<e.size();i++)//缩点,将询问边边权设为无穷小.有定理此时总边集e中在生成树中的边必定在l,r查询的生成树中 
        if (t.count(e[i].num)) union_n(e[i].u,e[i].v);//删掉一条边合并两个点
    for (int i=0;i<e.size();i++)
    {
        if (t.count(e[i].num)||e[i].del) continue;
        if (union_n(e[i].u,e[i].v)) e[i].uni=true,sum+=e[i].w;//这条边一定在生成树中 
    }
    init(n+10);//开始重构图 
    for (int i=0;i<e.size();i++) if (e[i].uni) union_n(e[i].u,e[i].v);//缩点 
    map<int,int> mp;
    int top=0;
    vector<edge> E;
    for (int i=1;i<=n;i++) 
        if (find(i)==i) 
            mp[i]=++top;
    for (int i=0;i<e.size();i++)
    {
        if (e[i].del||e[i].uni) continue;//不考虑一定在生成树中的和一定不在生成树中的边 
        edge T=e[i];T.u=mp[find(T.u)];T.v=mp[find(T.v)];
        E.push_back(T);
    }
    solve(l,mid,E,top,sum,l,0);
    solve(mid+1,r,E,top,sum,l,mid);
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1;i<=m;i++) {edge T;T.del=T.uni=0;scanf("%d%d%d",&T.u,&T.v,&T.w);T.num=i;e.push_back(T);}
    for (int i=1;i<=q;i++) scanf("%d%d",&que[i].x,&que[i].w);
    solve(1,q,e,n,0,1,0);
    for (int i=1;i<=q;i++) printf("%lld\n",ans[i]);
}

你可能感兴趣的:(cdq分治)