bzoj2001 [Hnoi2010]City 城市建设 动态最小生成树

昨晚水冬令营课件看到这题,感觉蛮有意思的,学习了一波,抽象式理解,今天又看了大佬的代码,彻底弄懂了这个东西。

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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值