题意
给你一张 n n n个点 m m m条边的无向联通图和 Q Q Q个询问,每次询问永久性的修改一条边的边权,问每次修改后改图的最小生成树的权值是多少;
题解
拿到题第一反应这不裸的线段树分治加个LCT吗,然后码码码码码,码了150多行交了一发wa,改了一下40,再来一下80,然后T了两组再也没涨过;一看题解原来是CDQ分治加神奇的缩点加速(
老前辈们的并查集为什么都是又用启发式合并又用路径压缩);
为什么说正解是神奇的缩点加速呢,这题是这么想的,CDQ分治递归处理子问题时先把区间里询问影响的边拿出来做 C o n t r a c t i o n Contraction Contraction- R e d u c t i o n Reduction Reduction操作:先把询问影响的边的权值设为 − I N F -INF −INF,然后做一次 M S T MST MST, M S T MST MST上的权值非询问边接下来一定会出现在 M S T MST MST上(考虑因为询问边的权值已经设成了最小,如果询问边能代替这些在 M S T MST MST上的非询问边,那么一定会先加进去的),所以直接把这些边的权值累加进答案里,然后将这些边构成的联通块缩成一个点,多余的边就直接删去只保留询不在联通块里的边,接下来再把询问边的边权生成 I N F INF INF,再做一次 M S T MST MST,只保留 M S T MST MST上的边和询问边;在这两次操作过后边数点数都大大减少,一直递归下去,到了 L = R L=R L=R时,点数边数已经非常少了,这个时候只需要永久修改当前询问边权,再做一次 M S T MST MST便能很快的得到答案;复杂度不会算
线段树分治+LCT
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+10;
int n,m,Q,top;
int last[N];
struct Edge {
int u,v,w,id;
}E[N>>1];
vector<Edge> V[N<<2];
struct Data {
int id,w,type;
Data () {}
Data (int a,int b,int c) :id(a),w(b),type(c) {}
}ST[N];
#define L son[rt][0]
#define R son[rt][1]
struct LinkCutTree {
bool rev[N];
int son[N][2],fa[N],w[N],ID[N];
bool Isroot(int rt) {
return son[fa[rt]][0]!=rt&&son[fa[rt]][1]!=rt;
}
bool Side(int rt,int f) {
return son[f][0]==rt?0:1;
}
void Pushup(int rt) {
ID[rt]=rt;
if(w[ID[L]]>w[ID[rt]]) ID[rt]=ID[L];
if(w[ID[R]]>w[ID[rt]]) ID[rt]=ID[R];
}
void Pushdown(int rt) {
if(rev[rt]) {
swap(L,R);
rev[L]^=1; rev[R]^=1; rev[rt]^=1;
}
}
void Push(int rt) {
if(!Isroot(rt)) Push(fa[rt]);
Pushdown(rt);
}
void Rotate(int rt) {
int f=fa[rt],to=Side(rt,f),ano=son[rt][to^1],fto=Side(f,fa[f]);
if(!Isroot(f)) son[fa[f]][fto]=rt;
fa[rt]=fa[f];
fa[ano]=f; son[f][to]=ano;
fa[f]=rt; son[rt][to^1]=f;
Pushup(f); Pushup(rt);
}
void Splay(int rt) {
Push(rt);
while(!Isroot(rt)) {
if(Side(rt,fa[rt])==Side(fa[rt],fa[fa[rt]])&&!Isroot(fa[rt])&&!Isroot(fa[fa[rt]])) Rotate(fa[rt]);
Rotate(rt);
}
}
void Access(int rt) {
for(int t=0;rt;t=rt,rt=fa[rt]) {
Splay(rt);
R=t;
Pushup(rt);
}
}
void Makeroot(int rt) {
Access(rt);
Splay(rt);
rev[rt]^=1;
}
void Split(int x,int y) {
Makeroot(x);
Access(y);
Splay(y);
}
void Cut(int x,int y) {
Split(x,y);
fa[x]=son[y][0]=0;
Pushup(y);
}
void Link(int x,int y) {
Makeroot(x);
fa[x]=y;
}
int Find(int rt) {
Access(rt);
Splay(rt);
while(L) rt=L;
return rt;
}
int Query(int x,int y) {
Split(x,y);
return ID[y];
}
}LCT;
void Modify(int l,int r,int o,int ql,int qr,Edge A) {
if(ql<=l&&r<=qr) {
V[o].push_back(A);
return;
}
int mid=l+r>>1;
if(ql<=mid) Modify(l,mid,o<<1,ql,qr,A);
if(qr>mid) Modify(mid+1,r,o<<1|1,ql,qr,A);
}
void DAC(int l,int r,int o,LL val) {
int now=top;
for(int i=0;i<(int)V[o].size();++i) {
int u=V[o][i].u,v=V[o][i].v,w=V[o][i].w,id=V[o][i].id;
int x=LCT.Find(u),y=LCT.Find(v);
if(x!=y) {
ST[++top]=Data(id,LCT.w[n+id],0); LCT.w[n+id]=w;
LCT.Link(u,n+id); LCT.Link(n+id,v);
val+=w;
}
else {
int t=LCT.Query(u,v);
if(LCT.w[t]>w) {
LCT.Cut(E[t-n].u,t); LCT.Cut(t,E[t-n].v);
ST[++top]=Data(t-n,LCT.w[t],1); val-=LCT.w[t];
ST[++top]=Data(id,LCT.w[n+id],0); LCT.w[n+id]=w;
LCT.Link(u,n+id); LCT.Link(n+id,v);
val+=w;
}
}
}
int mid=l+r>>1;
if(l==r) {
if(l>m) printf("%lld\n",val);
}
else DAC(l,mid,o<<1,val),DAC(mid+1,r,o<<1|1,val);
while(top!=now) {
int t=ST[top].id;
LCT.w[t+n]=ST[top].w; LCT.ID[t+n]=t+n;
if(!ST[top].type) {
LCT.Cut(E[t].u,t+n);
LCT.Cut(t+n,E[t].v);
}
else {
LCT.Link(E[t].u,t+n);
LCT.Link(t+n,E[t].v);
}
--top;
}
}
int main() {
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=m;++i) {
last[i]=1; E[i].id=i; LCT.ID[i+n]=i+n;
scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
}
Q+=m;
for(int i=m+1;i<=Q;++i) {
int k,d; scanf("%d%d",&k,&d);
Modify(1,Q,1,last[k],i-1,E[k]);
last[k]=i; E[k].w=d;
}
for(int i=1;i<=m;++i) Modify(1,Q,1,last[i],Q,E[i]);
DAC(1,Q,1,0);
return 0;
}
CDQ分治
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e4+10,M=5e4+10,INF=2e9;
int n,m,Q;
int F[N],W[M],ID[M];
LL ANS[M];
struct Data {
int id,w;
}A[M];
struct Edge {
int u,v,w,id;
}E[20][M],G[M],tmp[M];
int Find(int a) {
return F[a]==a?a:F[a]=Find(F[a]);
}
bool cmp(Edge A,Edge B) {
return A.w<B.w;
}
void Init(int m) {
for(int i=1;i<=m;++i) F[G[i].u]=G[i].u,F[G[i].v]=G[i].v;
}
LL Contraction(int &m) {
Init(m); sort(G+1,G+m+1,cmp);
int cnt=0; LL res=0;
for(int i=1;i<=m;++i) if(Find(G[i].u)!=Find(G[i].v)) F[F[G[i].u]]=F[G[i].v],tmp[++cnt]=G[i];
for(int i=1;i<=cnt;++i) F[tmp[i].u]=tmp[i].u,F[tmp[i].v]=tmp[i].v;
for(int i=1;i<=cnt;++i) if(tmp[i].w!=-INF) F[Find(tmp[i].u)]=Find(tmp[i].v),res+=tmp[i].w;
cnt=0;
for(int i=1;i<=m;++i) if(Find(G[i].u)!=Find(G[i].v)) {
tmp[++cnt]=G[i];
ID[G[i].id]=cnt;
tmp[cnt].u=F[G[i].u];
tmp[cnt].v=F[G[i].v];
}
m=cnt;
for(int i=1;i<=m;++i) G[i]=tmp[i];
return res;
}
void Reduction(int &m) {
Init(m); sort(G+1,G+m+1,cmp);
int cnt=0;
for(int i=1;i<=m;++i) {
if(Find(G[i].u)!=Find(G[i].v)) {
F[F[G[i].u]]=F[G[i].v];
tmp[++cnt]=G[i];
ID[G[i].id]=cnt;
}
else if(G[i].w==INF) {
tmp[++cnt]=G[i];
ID[G[i].id]=cnt;
}
}
m=cnt;
for(int i=1;i<=m;++i) G[i]=tmp[i];
}
void DAC(int l,int r,int dep,int m,LL res) {
if(l==r) W[A[l].id]=A[l].w;
for(int i=1;i<=m;++i) {
E[dep][i].w=W[E[dep][i].id];
G[i]=E[dep][i]; ID[G[i].id]=i;
}
if(l==r) {
Init(m); sort(G+1,G+m+1,cmp);
for(int i=1;i<=m;++i) if(Find(G[i].u)!=Find(G[i].v)) F[F[G[i].u]]=F[G[i].v],res+=G[i].w;
ANS[l]=res;
return;
}
for(int i=l;i<=r;++i) G[ID[A[i].id]].w=-INF;
res+=Contraction(m);
for(int i=l;i<=r;++i) G[ID[A[i].id]].w=INF;
Reduction(m);
for(int i=1;i<=m;++i) E[dep+1][i]=G[i];
int mid=l+r>>1;
DAC(l,mid,dep+1,m,res); DAC(mid+1,r,dep+1,m,res);
}
int main() {
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=m;++i) {
scanf("%d%d%d",&E[0][i].u,&E[0][i].v,&E[0][i].w);
E[0][i].id=i; W[i]=E[0][i].w;
}
for(int i=1;i<=Q;++i) scanf("%d%d",&A[i].id,&A[i].w);
DAC(1,Q,0,m,0);
for(int i=1;i<=Q;++i) printf("%lld\n",ANS[i]);
return 0;
}