昨晚水冬令营课件看到这题,感觉蛮有意思的,学习了一波,抽象式理解,今天又看了大佬的代码,彻底弄懂了这个东西。
WC2013顾昱洲在《浅谈一类分治算法》中提到了动态最小生成树的分治做法,我来梳理下我的理解。
这个算法有两个重要的操作:
①reduction:
对于一张图,reduction操作的目的是删除一定不会出现在最小生成树中的边,以此减小图的规模
流程:
我们假设对于当前这张图,有k条边待修改,将这k条边权值设为inf,做最小生成树,易知此时不在最小生成树中的边是不会在任何修改后出现在最小生成树中的,所以将这些边删去,得到新图。
②contraction:
对于一张图,contraction操作的目的是将一定会出现在最小生成树中的边的缩起来,即将这些边两端点缩为一个点,减小图的规模。
流程:
假设对于当前这张图,有k条边待修改,将这k条边权值设为-inf,做最小生成树,易知此时仍在最小生成树中的边在任何修改后都会出现在最小生成树中吗,所以将这些边缩起来,得到新图。
好了,操作梳理完了,怎么利用这些操作解决问题呢?直接暴力模拟每个状态的图肯定是不行的,即使将图的规模缩小到了最少的k+1个点和2k条边,这个复杂度也是不能接受的。
但是显然可以发现,一条边的待修改状态存在于一个区间内,考虑同一张图P,对于在区间[l,r]中的修改进行reduction和contraction操作得到新图S,对在区间[x,y]中的修改进行reduction和contraction操作的到的新图T,若[x,y]∈[l,r],T必然是S的子图,所以用cdq分治即可。
代码:
#include<bits/stdc++.h>
#define ll long long
#define inf 1ll<<50
#define N 100005
using namespace std;
inline int read()
{int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;
}
int n,m,q,a[N],sum[50];
int f[N],c[N];ll ans[N];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
struct node{int x,y;}p[N];
struct edge{int x,y,pos;ll c;}t[N],e[50][N],d[N];
bool operator < (edge a,edge b){return a.c<b.c;}
void clear(int x)
{for(int i=1;i<=x;i++)f[d[i].x]=d[i].x,f[d[i].y]=d[i].y;
}
void contraction(int &tot,ll &adt)
{int tmp=0;sort(d+1,d+1+tot);clear(tot);for(int i=1;i<=tot;i++){int fx=find(d[i].x),fy=find(d[i].y);if(fx==fy)continue;f[fx]=fy;t[++tmp]=d[i];}for(int i=1;i<=tmp;i++)f[t[i].x]=t[i].x,f[t[i].y]=t[i].y;for(int i=1;i<=tmp;i++){if(t[i].c==-inf)continue;int fx=find(t[i].x),fy=find(t[i].y);adt+=t[i].c,f[fx]=fy;}tmp=0;for(int i=1;i<=tot;i++){int fx=find(d[i].x),fy=find(d[i].y);if(fx==fy)continue;t[++tmp]=d[i];t[tmp].x=find(d[i].x);t[tmp].y=find(d[i].y);c[d[i].pos]=tmp;}tot=tmp;for(int i=1;i<=tot;i++)d[i]=t[i];
}
void reduction(int &tot)
{sort(d+1,d+1+tot);clear(tot);int tmp=0;for(int i=1;i<=tot;i++){int fx=find(d[i].x),fy=find(d[i].y);if(fx==fy){if(d[i].c==inf)t[++tmp]=d[i],c[d[i].pos]=tmp;continue;}f[fx]=fy;t[++tmp]=d[i],c[d[i].pos]=tmp;}tot=tmp;for(int i=1;i<=tot;i++)d[i]=t[i];
}
void solve(int l,int r,int now,ll adt)
{int tot=sum[now];if(l==r)a[p[l].x]=p[l].y;for(int j=1;j<=tot;j++)e[now][j].c=a[e[now][j].pos],d[j]=e[now][j],c[e[now][j].pos]=j;if(l==r){clear(tot);sort(d+1,d+1+tot);for(int j=1;j<=tot;j++){int fx=find(d[j].x),fy=find(d[j].y);if(fx==fy)continue;f[fx]=fy;adt+=d[j].c;}ans[l]=adt;return ;}int mid=(l+r)>>1;for(int i=l;i<=r;i++)d[c[p[i].x]].c=-inf;contraction(tot,adt);for(int i=l;i<=r;i++)d[c[p[i].x]].c=inf;reduction(tot);sum[now+1]=tot;for(int i=1;i<=tot;i++)e[now+1][i]=d[i];solve(l,mid,now+1,adt);solve(mid+1,r,now+1,adt);
}
int main()
{n=read(),m=read(),q=read();for(int i=1;i<=m;i++){e[0][i].x=read(),e[0][i].y=read();e[0][i].pos=i,a[i]=e[0][i].c=read();}sum[0]=m;for(int i=1;i<=q;i++)p[i].x=read(),p[i].y=read();solve(1,q,0,0);for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
}