LCT动态维护最小生成树的典例
这个都是套路,倒序处理,将删边变成加边
再将边换成点,然后就可以做到点权维护边权信息
说一下加边的过程大概是
一、首先最小生成树,保证初始最优
二、对于新加入的一条边,找出原x->y链上的最大边,替换
第二点的实现比较有意思
因为是在树上加边,所以一定会形成一个环,那么我们要使边权和最小,就要删边
分为两种情况,一种是新加入的边比换上的最大边还要大,就不用管
但是如果比最大边小,就Cut掉最大边,Link新边即可
如何维护最大边权呢
上面说了,将边换成点,然后就愉快的变成了统计路径上点的最大值
说起来很简单,然后边换点的操作实在是有点丧心病狂
让我改了N久…
大部分都是板子,主要讲讲主程序的预处理和新的pushup
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 200007;
struct edge
{
int from,to,w,id,exist;
}e[maxn];
struct node
{
int x,y,ans,id,opt;
}task[maxn];
int n,m,q,lazy[maxn],c[maxn][2],Roundmax[maxn],val[maxn],fa[maxn],st[maxn],top;
bool isroot(int k)
{
return !fa[k]||(c[fa[k]][0]!=k&&c[fa[k]][1]!=k);
}
void pushdown(int k)
{
if(lazy[k])
{
lazy[k]^=1;
lazy[c[k][0]]^=1;
lazy[c[k][1]]^=1;
swap(c[k][0],c[k][1]);
}
}
void pushup(int k)
{
Roundmax[k]=k;//初始最大值就是该边本身
if(val[ Roundmax[c[k][0]] ]>val[Roundmax[k]]) Roundmax[k]=Roundmax[c[k][0]];
// 左儿子维护的最大值大于自身,更新
if(val[ Roundmax[c[k][1]] ]>val[Roundmax[k]]) Roundmax[k]=Roundmax[c[k][1]];
// 右儿子维护的最大值大于自身,更新
// 注意,维护的是编号,通过编号访问边权
}
void rotate(int k)
{
int y=fa[k],z=fa[y];
int r=c[y][0]==k;
if(!isroot(y))
if(c[z][0]==y) c[z][0]=k;
else c[z][1]=k;
fa[k]=z;fa[y]=k;c[y][!r]=c[k][r];
fa[c[k][r]]=y;c[k][r]=y;
pushup(y);pushup(k);
}
void splay(int k)
{
st[top=1]=k;
int x1=k;
while(!isroot(x1))st[++top]=x1=fa[x1];
while(top) pushdown(st[top--]);
while(!isroot(k))
{
int y=fa[k],z=fa[y];
if(!isroot(y))
rotate(((c[z][0]==y)^(c[y][0]==k))?k:y);
rotate(k);
}
}
void access(int k)
{
for(int y=0;k;y=k,k=fa[k])
{
splay(k);
c[k][1]=y;
pushup(k);
}
}
void makeroot(int x)
{
access(x);
splay(x);
lazy[x]^=1;
}
void link(int x,int y)
{
makeroot(x);
fa[x]=y;
}
void cut(int x,int y)
{
makeroot(x);//split
access(y);
splay(y);
fa[x]=c[y][0]=0;
}
int query(int x,int y)
{
makeroot(x);
access(y);
splay(y);// 实际上就是split出x->y的路径
return Roundmax[y];返回最大值
}
bool cmp1(edge a,edge b){return a.from==b.from?a.to<b.to:a.from<b.from;}
bool cmp2(edge a,edge b){return a.w<b.w;}
bool cmp3(edge a,edge b){return a.id<b.id;}
int findedge(int u,int v)// 这里很蠢的写了一个手动二分,为了找到删除的边
{ // 实际上重载一下运算符,直接lower_bound就好了
int l=1,r=m;
while(l<=r)
{
int mid=(l+r)/2;
if(e[mid].from<u||(e[mid].from==u&&e[mid].to<v))l=mid+1;
else if(e[mid].from==u&&e[mid].to==v)return mid;
else r=mid-1;
}
}
int f[maxn];
int find(int x){if(f[x]==x)return f[x]; else return f[x]=find(f[x]);}// Kruskal
void Kru()
{
for(int i=1;i<=n;++i) f[i]=i;
sort(e+1,e+m+1,cmp3);
int tot=0;
for(int i=1;i<=m;++i)
{
if(e[i].exist) continue;
int x=find(e[i].from);
int y=find(e[i].to);
if(x==y)continue;
tot++;f[x]=y;
link(e[i].from,i+n);// 边换点
link(e[i].to,i+n);
if(tot==n-1) return;
}
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;++i)
{
scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].w);
if(e[i].from>e[i].to)swap(e[i].from,e[i].to);
}
sort(e+1,e+m+1,cmp2);// 排序,然后对边编号
for(int i=1;i<=m;i++)
{
e[i].id=i;
val[n+i]=e[i].w;// 给边权为点权
Roundmax[n+i]=n+i;
}
sort(e+1,e+m+1,cmp1);
for(int i=1;i<=q;i++)
{
scanf("%d%d%d",&task[i].opt,&task[i].x,&task[i].y);
if(task[i].opt==2)
{
if(task[i].x>task[i].y) swap(task[i].x,task[i].y);
int id=findedge(task[i].x,task[i].y);// 倒序读入,将删边改成加边
task[i].id=e[id].id; // 这里很傻的写了手动二分
e[id].exist=1; // 删除标记
}
}
Kru();// 生成树
for(int i=q;i>=1;i--)
{
if(task[i].opt==1)
task[i].ans=val[query(task[i].x,task[i].y)];// 返回的是边的编号
else
{
int YesChange=query(task[i].x,task[i].y);// 得到最大值
if(val[YesChange]>val[task[i].id+n])// 达成替换条件,换边
{
cut(e[YesChange-n].from,YesChange);// 删旧
cut(e[YesChange-n].to,YesChange);
link(task[i].x,task[i].id+n); // 连新
link(task[i].y,task[i].id+n);
}
}
}
for(int i=1;i<=q;i++) // 输出,撒花
if(task[i].opt==1)
printf("%d\n",task[i].ans);
return 0;// ^_^结束
}