[冲刺国赛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("");
}
}