LCT
推荐:https://www.cnblogs.com/flashhu/p/8324551.html
分析
用来处理有删边但没有子树操作的问题
模板
inline bool nrt(int x) { //判断x是否为splay中的根,认父不认子
return c[f[x]][0]==x||c[f[x]][1]==x;
}
void rotate(int x) {
int y=f[x],z=f[y],k=c[y][1]==x;
if(nrt(y)) c[z][c[z][1]==y]=x; f[x]=z;
if(c[x][!k]) f[c[x][!k]]=y; c[y][k]=c[x][!k];
c[x][!k]=y,f[y]=x;
up(y);
}
void splay(int x) {
int y=x,z=0;
st[++z]=x;
while(nrt(y)) st[++z]=f[y],y=f[y];
while(z) down(st[z--]);
while(nrt(x)) {
int y=f[x],z=f[y];
if(nrt(y)) {
rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
}
rotate(x);
}
up(x);
}
access:打通x到根的道路,splay中的键值是深度
void access(int x) {
for(int y=0;x;y=x,x=f[x]) {
splay(x),c[x][1]=y,f[y]=x,up(x);
}
}
makeroot:将x变成根
inline void pushr(int x) {
swap(c[x][0],c[x][1]);
rev[x]^=1;
}
void makert(int x) {
access(x),splay(x);
pushr(x);
}
split:将x,y路径提到一个splay中,以y为根
void split(int x,int y) {
makert(x),access(y),splay(y);
}
*findroot:寻找x的根(树上)
int findrt(int x) {
access(x),splay(x);
for(;c[x][0];x=c[x][0]) down(x);
splay(x); //保证时间复杂度
return x;
}
link:连接
void link(int x,int y) {
makert(x);
*if(findrt(y)==x) return;
f[x]=y;
}
cut:删除
void cut(int x,int y) {
makert(x),access(y),splay(y);
*if(c[y][0]!=x||c[x][1]||f[x]!=y) return;
f[x]=0,c[y][0]=0; up(y);
}
基槽1
可以动态维护最小(大)生成树
例题1
[Luogu P2387 [NOI2014] 魔法森林](http://www.cnblogs.com/ https://www.luogu.com.cn/problem/P2387)
按a排序后动态维护最小生成树即可,裂点成边
#include<bits/stdc++.h>
const int INF=1e9;
using namespace std;
const int N=2e5+5;
int n,m;
struct A{int u,v,a,b; }E[N];
inline bool cmp(A i,A j) {
return i.a<j.a;
}
namespace LCT {
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
int ch[N][2],c[N],s[N],f[N],cnt,r[N],a[N],st[N];
inline void pushr(int u) {
swap(ls(u),rs(u));
r[u]^=1;
}
inline void down(int u) {
if(r[u]) {
if(ls(u)) pushr(ls(u));
if(rs(u)) pushr(rs(u));
r[u]=0;
}
}
inline void up(int u) {
s[u]=a[u]; c[u]=u;
if(ls(u)&&s[ls(u)]>s[u]) {
s[u]=s[ls(u)];
c[u]=c[ls(u)];
}
if(rs(u)&&s[rs(u)]>s[u]) {
s[u]=s[rs(u)];
c[u]=c[rs(u)];
}
}
inline bool nrt(int x) {
return ls(f[x])==x||rs(f[x])==x;
}
void rotate(int x) {
int y=f[x],z=f[y]; bool k=(rs(y)==x);
if(nrt(y)) ch[z][rs(z)==y]=x; f[x]=z;
if(ch[x][!k]) f[ch[x][!k]]=y; ch[y][k]=ch[x][!k];
ch[x][!k]=y; f[y]=x;
up(y);
}
void splay(int x) {
int y=x,z=0;
st[++z]=y;
for(;nrt(y);y=f[y]) st[++z]=f[y];
for(;z;z--) down(st[z]);
while(nrt(x)) {
y=f[x],z=f[y];
if(nrt(y)) rotate(((rs(y)==x)^(rs(z)==y))?x:y);
rotate(x);
}
up(x);
}
void access(int x) {
for(int y=0;x;y=x,x=f[x]) {
splay(x),rs(x)=y,up(x);
}
}
void makert(int u) {
access(u),splay(u);
pushr(u);
}
void link(int u,int v,int k) {
makert(u);
s[++cnt]=k,a[cnt]=k,c[cnt]=cnt;
f[u]=cnt,f[cnt]=v;
}
int find(int u) {
access(u),splay(u);
for(;ls(u);u=ls(u)) down(u);
splay(u);
return u;
}
void split(int u,int v) {
makert(u); access(v),splay(v);
}
void cut(int u) {
makert(u),down(u);
f[ls(u)]=f[rs(u)]=0;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
scanf("%d%d%d%d",&E[i].u,&E[i].v,&E[i].a,&E[i].b);
}
sort(E+1,E+m+1,cmp);
int ans=INF; LCT::cnt=n;
for(int i=1;i<=m;i++) {
int u=E[i].u,v=E[i].v;
int t1=LCT::find(u),t2=LCT::find(v);
if(t1!=t2) {
LCT::link(u,v,E[i].b);
} else {
LCT::split(u,v);
if(LCT::s[v]>E[i].b) {
LCT::cut(LCT::c[v]);
LCT::link(u,v,E[i].b);
}
}
if(LCT::find(1)==LCT::find(n)) {
LCT::split(1,n);
ans=min(ans,E[i].a+LCT::s[n]);
}
}
printf("%d\n",ans==INF?-1:ans);
return 0;
}
例题2
枚举最大值,然后维护最大生成树
注意:multiset的erase会删掉全部
#include<bits/stdc++.h>
const int INF=1e9;
using namespace std;
const int N=1e6+5;
int c[N][2],n,m,s[N],loc[N],st[N],f[N],rev[N],a[N];
int tot;
struct A{int x,y; };
vector<A>t[N];
inline bool nrt(int x) {
return c[f[x]][0]==x|c[f[x]][1]==x;
}
inline void R(int x) {
swap(c[x][0],c[x][1]);
rev[x]^=1;
}
inline void down(int x) {
if(rev[x]) {
if(c[x][0]) R(c[x][0]);
if(c[x][1]) R(c[x][1]);
rev[x]=0;
}
}
inline void up(int x) {
s[x]=max(max(s[c[x][0]],s[c[x][1]]),a[x]);
if(a[x]==s[x]) loc[x]=x;
else if(s[x]==s[c[x][0]]) loc[x]=loc[c[x][0]];
else loc[x]=loc[c[x][1]];
}
void rotate(int x) {
int y=f[x],z=f[y],k=c[y][1]==x;
if(nrt(y)) c[z][c[z][1]==y]=x; f[x]=z;
if(c[x][!k]) f[c[x][!k]]=y; c[y][k]=c[x][!k];
c[x][!k]=y,f[y]=x;
up(y);
}
void splay(int x) {
int y=x,z=0;
st[++z]=y;
while(nrt(y)) st[++z]=f[y],y=f[y];
while(z) down(st[z--]);
while(nrt(x)) {
y=f[x],z=f[y];
if(nrt(y)) {
rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
}
rotate(x);
}
up(x);
}
void access(int x) {
for(int y=0;x;y=x,x=f[x]) {
splay(x),c[x][1]=y,up(x);
}
}
void makert(int x) {
access(x),splay(x);
R(x);
}
void split(int x,int y) {
makert(x),access(y),splay(y);
}
void link(int x,int y,int k) {
makert(x);
f[x]=++tot,f[tot]=y;
s[tot]=a[tot]=k,loc[tot]=tot;
}
void cut(int x) {
makert(x); down(x);
f[c[x][0]]=0,f[c[x][1]]=0;
}
int find(int x) {
access(x),splay(x);
while(c[x][0]) down(x=c[x][0]);
splay(x);
return x;
}
multiset<int>S;
int main() {
scanf("%d%d",&n,&m); int mx=0;
for(int i=1;i<=m;i++) {
int x,y,a; scanf("%d%d%d",&x,&y,&a);
if(x!=y) t[a].push_back((A){x,y}),mx=max(mx,a);
}
int ans=INF; tot=n;
for(int i=mx;i;i--) {
if(!t[i].empty()) {
for(int j=0;j<t[i].size();j++) {
int x=t[i][j].x,y=t[i][j].y;
if(find(x)==find(y)) {
split(x,y);
if(s[y]!=i) {
multiset<int>::iterator it;
it=S.
lower_bound(s[y]);
S.erase(it),cut(loc[y]);
S.insert(i),link(x,y,i);
}
} else S.insert(i),link(x,y,i);
}
if(S.size()==n-1) {
multiset<int>::iterator it;
it=S.end(); it--;
ans=min(ans,(*it)-i);
}
}
}
printf("%d\n",ans==INF?-1:ans);
return 0;
}
例题3
联通块数量=\(n-\)生成树边数
不妨将询问按\(r\)排序,然后维护以编号为边权的最大生成树
考虑如何求出答案,可以用树状数组维护最大生成树上的编号情况
#include<bits/stdc++.h>
const int INF=1e9;
using namespace std;
const int N=2e5+5,M=4e5+5;
int n,m,Q,co[M],c[M][2],rev[M],a[M],f[M],mn[M],st[M],ans[N],E1[N],E2[N];
struct A{int a,b; };
vector<A>q[N];
void add(int x,int k) {
for(int i=x;i;i-=i&-i) co[i]+=k;
}
int ask(int x) {
int ret=0;
for(int i=x;i<=n+m;i+=i&-i) ret+=co[i];
return ret;
}
inline void pushr(int x) {
swap(c[x][0],c[x][1]);
rev[x]^=1;
}
inline bool nrt(int x) {
return c[f[x]][0]==x||c[f[x]][1]==x;
}
inline void up(int x) {
mn[x]=a[x];
if(c[x][0]) mn[x]=min(mn[x],mn[c[x][0]]);
if(c[x][1]) mn[x]=min(mn[x],mn[c[x][1]]);
}
inline void down(int x) {
if(rev[x]) {
if(c[x][0]) pushr(c[x][0]);
if(c[x][1]) pushr(c[x][1]);
rev[x]=0;
}
}
void rotate(int x) {
int y=f[x],z=f[y],k=c[y][1]==x;
if(nrt(y)) c[z][c[z][1]==y]=x; f[x]=z;
if(c[x][!k]) f[c[x][!k]]=y; c[y][k]=c[x][!k];
c[x][!k]=y,f[y]=x;
up(y);
}
void splay(int x) {
int y=x,z=0; st[++z]=x;
while(nrt(y)) st[++z]=f[y],y=f[y];
while(z) down(st[z]),z--;
while(nrt(x)) {
y=f[x],z=f[y];
if(nrt(y)) {
rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
}
rotate(x);
}
up(x);
}
void access(int x) {
for(int y=0;x;y=x,x=f[x]) {
splay(x); c[x][1]=y; up(x);
}
}
void makert(int x) {
access(x),splay(x);
pushr(x);
}
void split(int x,int y) {
makert(x); access(y),splay(y);
}
void cut(int x,int y) {
split(x,y);
f[x]=c[y][0]=0;
up(y);
}
int findrt(int x) {
access(x),splay(x);
down(x);
while(c[x][0]) x=c[x][0],down(x);
splay(x);
return x;
}
void link(int x,int y,int i) {
int New=i+n; a[New]=i;
if(findrt(x)==findrt(y)) {
split(x,y);
int t=mn[y];
add(t,-1);
cut(E1[t],t+n),cut(t+n,E2[t]);
}
makert(x); f[x]=New;
makert(New); f[New]=y;
add(a[New],1);
}
int main() {
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=n;i++) a[i]=INF;
for(int i=1;i<=m;i++) {
scanf("%d%d",&E1[i],&E2[i]);
}
for(int i=1;i<=Q;i++) {
int u,v; scanf("%d%d",&u,&v);
q[v].push_back((A){u,i});
}
for(int i=1;i<=m;i++) {
int u=E1[i],v=E2[i];
if(u!=v) link(u,v,i);
for(auto t:q[i]) {
ans[t.b]=n-ask(t.a);
}
}
for(int i=1;i<=Q;i++) {
printf("%d\n",ans[i]);
}
return 0;
}