[冲刺国赛2022] 最小生成树

一、题目

有编号为 \(0,1,2...n\)\(n+1\) 个点,一共有两类边:

  • \(n\) 条边,第 \(i\) 条连接 \((0,i)\),边权为 \(a_i\)
  • \(m\) 条边,在 \((u,v)\) 之间连一条边权为 \(w\) 的边。

\(q\) 次修改,每次将 \(a_x\) 修改为 \(y\),每次修改之后求出最小生成树,注意修改不独立

\(n,m,q\leq 3\cdot 10^5\)

二、解法

线段树分治加 link-cut-tree 是我觉得最垃圾的做法,毫无美感可言,所以也没给什么分。

首先考虑如果第二类边是链怎么做?直接对链建立线段树,对于每个区间 \([l,r]\),我们只需要维护 t[0/1][0/1] 表示考虑区间中的所有一类边和二类边,\(l\) 的连通块是否与 \(0\) 连通;\(r\) 的连通块是否与 \(0\) 连通;区间内的其它连通块都已经与 \(0\) 连通。达到这个目的需要花费的最小代价。

在合并时,需要处理中间两个连通块。如果都不与 \(0\) 连通,那么不合法;如果都与 \(0\) 连通,不需要花费代价就可以合并;如果只有一边与 \(0\) 连通,那么需要花费中间那条二类边的代价。

进一步解释上面的方法,考虑 \(l,r\) 的连通块具体是什么样子不需要关心,只需要关心与 \(0\) 的连通性就可以支持合并,因为中间那条边的权值都是一样的。另外对于叶子节点的初始化,只有 t[1][1] 需要设置为 \(a_i\),其他都设置成 \(0\) 就行了,正确性不难理解。

\(\tt UPD\):上面的方法也可以理解成,设 \(dp_{i,0/1}\) 表示考虑前 \(i\) 个点,\(i\) 的连通块和 \(0\) 不连通 \(/\) 连通。那么可以直接动态 \(dp\)

推广到普遍的情况,考虑找出第二类边的等效链。我们对第二类边单独跑 kruskal,对于每个连通块都维护其对应的等效链,合并两个连通块的时候,我们也合并对应的等效链,直接把两条链的端点用这条边接起来就行了。

这样做为什么是对的呢?考虑出错的情形是:这条边原来连接 \((u,v)\),但在等效链上连接了 \((x,y)\),如果 \((u,x)/(v,y)\) 不在同一个连通块中就可能出错。但是考虑第一类边时,如果这条二类边起作用,那么根据 kruskal 的过程,这条边的作用环境仍然是不变的(也就是多考虑了一些边,这条边起作用时必定有 \((u,x)\)\((v,y)\) 都连通的性质)

那么跑完 kruskal 之后直接上线段树维护,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 300005;
const int inf = 0x3f3f3f3f;
#define ll long long
#define pb push_back
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
void write(ll x)
{
	if(x>=10) write(x/10);
	putchar(x%10+'0');
}
int n,m,q,a[M],b[M],fa[M],p[M],id[M];
vector<int> s[M],w[M];ll t[M<<2][2][2];
struct node{int u,v,c;}e[M];
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void merge(int u,int v,int c)
{
	u=find(u);v=find(v);if(u==v) return ;
	if(s[u].size()<s[v].size()) swap(u,v);
	fa[v]=u;for(int x:s[v]) s[u].pb(x);
	w[u].pb(c);for(int x:w[v]) w[u].pb(x);
}
void up(int i,int zxy)
{
	int l=i<<1,r=i<<1|1;
	memset(t[i],0x3f,sizeof t[i]);
	for(int a=0;a<2;a++) for(int b=0;b<2;b++)
		for(int c=0;c<2;c++) if(b|c) for(int d=0;d<2;d++)
			t[i][a][d]=min(t[i][a][d],t[l][a][b]
			+t[r][c][d]+((b&c)?0:zxy));
}
void build(int i,int l,int r)
{
	if(l==r)
	{
		for(int j=0;j<2;j++)
			for(int k=0;k<2;k++)
				t[i][j][k]=(j&k)?a[p[l]]:0;
		return ;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	up(i,b[mid]);
}
void ins(int i,int l,int r,int x)
{
	if(l==r)
	{
		for(int j=0;j<2;j++)
			for(int k=0;k<2;k++)
				t[i][j][k]=(j&k)?a[p[l]]:0;
		return ;
	}
	int mid=(l+r)>>1;
	if(mid>=x) ins(i<<1,l,mid,x);
	else ins(i<<1|1,mid+1,r,x);
	up(i,b[mid]);
}
signed main()
{
	freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),fa[i]=i,s[i].pb(i);
	for(int i=1;i<=m;i++)
		e[i].u=read(),e[i].v=read(),e[i].c=read();
	sort(e+1,e+1+m,[&](node a,node b){return a.c<b.c;});
	for(int i=1;i<=m;i++)
		merge(e[i].u,e[i].v,e[i].c);
	for(int i=1;i<n;i++)
		merge(i,i+1,inf);
	int rt=find(1),cnt=0;
	for(int x:s[rt]) p[++cnt]=x;
	cnt=0;for(int x:w[rt]) b[++cnt]=x;
	for(int i=1;i<=n;i++) id[p[i]]=i;
	build(1,1,n);q=read();
	while(q--)
	{
		int x=read(),y=read();a[x]=y;
		ins(1,1,n,id[x]);
		write(t[1][1][1]);puts("");
	}
}
posted @ 2022-07-19 22:32  C202044zxy  阅读(227)  评论(0编辑  收藏  举报