关于珂朵莉树
前言
几个月前学长就讲过珂朵莉树,当时对指针和STL有股莫名的畏惧,咕到现在才入门
定义
用\(STL\)容器维护颜色均摊段
可以理解为不定长度的分块-但一定要有区间推平操作+随机数据
总体来说,是不稳定的优雅的暴力
思想
举个例子这个序列
如何维护每种值所代表的信息?
一种很自然的想法是把相同的值全部放到一起
然后维护左右端点,进行直接查询,推出结构体储存
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;
}