关于珂朵莉树

前言

几个月前学长就讲过珂朵莉树,当时对指针和STL有股莫名的畏惧,咕到现在才入门

定义

\(STL\)容器维护颜色均摊段
可以理解为不定长度的分块-但一定要有区间推平操作+随机数据
总体来说,是不稳定的优雅的暴力

思想

举个例子image这个序列
如何维护每种值所代表的信息?
一种很自然的想法是把相同的值全部放到一起
image
然后维护左右端点,进行直接查询,推出结构体储存

struct Node{
	int lz,rz;mutable int val;
	Node(int lz,int rz=0,int val=0):lz(lz),rz(rz),val(val){};
	inline bool operator<(const Node &x)const{
		return lz<x.lz;
	}
};
  • lz: 左边界下标
  • rz: 右边界下标
  • val: 当前区间的值
  • Node(): 初始化,只需要左值
  • 重载<运算符,按照左端点排序

然后就是最重要的\(split\)(分割)函数了

inline auto split(int pos){
	auto it=s.lower_bound(Node(pos));
	if(it!=s.end()&&it->lz==pos) return it;
	it--;
	if(it->rz<pos) return s.end();
	int l=it->lz,r=it->rz,w=it->val;
	s.rease(it);
	s.insert(Node(l,pos-1,w));
	return s.insert(Node(pos,r,w)).first;
}

先解释一下,这个\(auto\)的全写是

set<Node>::iterator

也就是set的迭代器的意思
逐行解释一下
首先,对于每次推平操作,会有一些Node被合并,也会有一些Node被拆开,\(split\)就是找一个位置pos,把pos对应的Node分割成\([l,pos-1]\)\([pos,r]\)两个区间,如果pos直接是一个区间的开头或结尾,直接返回区间即可

inline auto split(int pos){// 
	auto it=s.lower_bound(Node(pos));//查询pos所在区间 
	if(it!=s.end()&&it->lz==pos) return it;// pos是该区间开头时 
	it--;//将it往前挪 
	if(it->rz<pos) return s.end();// pos太大,直接返回s的最后 
	int l=it->lz,r=it->rz,w=it->val;
	s.erase(it);//原来的区间直接删掉 
	s.insert(Node(l,pos-1,w));//分割成两个小区间 
	return s.insert(Node(pos,r,w)).first;
}

然后就是合并操作,我喜欢把函数命名为\(gto(get-together)\)

inline void gto(int l,int r,int w){//将区间l~r变成整块值为w的块
	auto itr=split(r+1),itl=split(l);//找到左右端点
	s.erase(itl,itr);//删除原区间
	s.insert(Node(l,r,w));//加入新区间
}

为什么先获取 \(itr\) 再获取 \(itl\) ?
因为如果先分割左边的l,右边的r可能会错位,而先后面再前面就不会错位
基本操作就是这两种,然后就是愉快的暴力拉

例题

1.CF896C Willem, Chtholly and Seniorious

珂朵莉树的起源,就不详细啰嗦了,哪里的题解比我讲得好的多

2.P4315 月下“毛景树”

看到区间推平,珂朵莉!

#include<bits/stdc++.h>
#define int long long
#define ddq set<Node>::iterator
#define fi first
#define se second
#define mk(a,b) make_pair(a,b)
using namespace std;
const int M=2e5+110;

inline int read(){
    int sum=0,k=1;char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')k=-1;c=getchar();}
    while(c>='0'&&c<='9'){sum=sum*10+c-48;c=getchar();}
    return sum*k;
}

struct Node{
    int lz,rz;mutable int val;
    Node(int lz,int rz=0,int val=0):lz(lz),rz(rz),val(val){};
    inline bool operator<(const Node &a)const{
        return lz<a.lz;
    }
};set<Node>s;

inline ddq split(int pos){
	ddq it=s.lower_bound(Node(pos));
	if(it->lz==pos&&it!=s.end()) return it;
	it--;
	if(it->rz<pos) return s.end();
	int l=it->lz,r=it->rz,w=it->val;
	s.erase(it);
	s.insert(Node(l,pos-1,w));
	return s.insert(Node(pos,r,w)).fi;
}

inline void gto(int l,int r,int val){
    if(l>r) return;
    ddq itr=split(r+1),itl=split(l);
    s.erase(itl,itr);
    s.insert(Node(l,r,val));
}

inline void ad(int l,int r,int val){//暴力遍历之间的每一个区间加值
    if(l>r) return;
    ddq itr=split(r+1),itl=split(l);
    for(ddq it=itl;it!=itr;it++)
        it->val+=val;
}

inline int ask(int l,int r){//暴力查找
    if(l>r) return -1e18;
    int maxx=-1e18;
    ddq itr=split(r+1),itl=split(l);
    for(ddq it=itl;it!=itr;it++)
        maxx=max(maxx,it->val);
    return maxx;
}

vector<pair<int,int>> Ed[M];
int a[M],Deep[M],Fa[M],Son[M],Siz[M],Id[M],Top[M],Ti=0;
pair<int,int> edge[M]; // 存储每条边的两个端点

inline void Adde(int u,int v,int w){
    Ed[u].push_back(mk(v,w));
    Ed[v].push_back(mk(u,w));
}

inline void dfs1(int u,int f){
    Fa[u]=f;Deep[u]=Deep[f]+1;
    Siz[u]=1;
    for(auto i:Ed[u]){
        int v=i.fi,w=i.se;
        if(v==f) continue;
        a[v]=w; // 边权存在子节点上
        dfs1(v,u);
        Siz[u]+=Siz[v];
        if(Siz[Son[u]]<Siz[v])
            Son[u]=v;
    }
}

inline void dfs2(int u,int topf){
    Id[u]=++Ti;Top[u]=topf;
    if(!Son[u]) return;
    dfs2(Son[u],topf);
    for(auto i:Ed[u]){
        int v=i.fi;
        if(v==Fa[u]||v==Son[u]) continue;
        dfs2(v,v);
    }
}

inline void ch(int k,int w){//第k条树枝 
    int u=edge[k].fi,v=edge[k].se;
    if(Deep[u]<Deep[v]) swap(u,v);
    gto(Id[u],Id[u],w);
}

inline void co(int u,int v,int w){
    while(Top[u]!=Top[v]){
        if(Deep[Top[u]]<Deep[Top[v]]) swap(u,v);
        gto(Id[Top[u]],Id[u],w);
        u=Fa[Top[u]];
    }
    if(Deep[u]>Deep[v]) swap(u,v);
    if(u!=v) gto(Id[u]+1,Id[v],w);
}

inline void Add(int u,int v,int w){
    while(Top[u]!=Top[v]){
        if(Deep[Top[u]]<Deep[Top[v]]) swap(u,v);
        ad(Id[Top[u]],Id[u],w);
        u=Fa[Top[u]];
    }
    if(Deep[u]>Deep[v]) swap(u,v);
    if(u!=v) ad(Id[u]+1,Id[v],w);
}

inline int Max(int u,int v){
    int maxx=-1e18;
    while(Top[u]!=Top[v]){
        if(Deep[Top[u]]<Deep[Top[v]]) swap(u,v);
        maxx=max(maxx,ask(Id[Top[u]],Id[u]));
        u=Fa[Top[u]];
    }
    if(Deep[u]>Deep[v]) swap(u,v);
    if(u!=v) maxx=max(maxx,ask(Id[u]+1,Id[v]));
    return maxx;
}
signed main(){
    int n=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read(),w=read();
        edge[i]=mk(u,v); // 存储边的两个端点
        Adde(u,v,w);
    }
    dfs1(1,0);dfs2(1,1);
    for(int i=1;i<=n;i++) s.insert(Node(Id[i],Id[i],a[i]));//新序列的珂朵莉树,对Id[i]建
    while(1){
        string cs;cin>>cs;
        if(cs=="Stop") break;
        if(cs=="Change"){
            int k=read(),w=read();
            ch(k,w);
        }
        else if(cs=="Cover"){
            int u=read(),v=read(),w=read();
            co(u,v,w);
        }
        else if(cs=="Add"){
            int u=read(),v=read(),w=read();
            Add(u,v,w);
        }
        else if(cs=="Max"){
            int u=read(),v=read();
            printf("%lld\n",Max(u,v));
        }
    }
    return 0;
}
posted @ 2025-07-17 20:20  rerecloud  阅读(20)  评论(0)    收藏  举报