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;
}