【模板】数据结构:左偏树(可并堆) 、k 短路、最小树形图
左偏树
左偏树是一种支持可持久化和合并的二叉堆。记 \(dis_u\) 表示点 \(u\) 到最近的叶子的距离,那么左偏树时刻满足 \(dis_u=dis_{rc}+1\land dis_{lc}\geq dis_{rc}\) 且它的深度(\(dis_u\))是严格 \(O(\log n)\)。
左偏树
template<int N,class T> struct leftree{
int ch[N+10][2],dis[N+10],tot; T val[N+10];
leftree():tot(0){dis[0]=-1;}
int newnode(T x){int p=++tot;return val[p]=x,ch[p][0]=ch[p][1]=0,dis[p]=0,p;}
int merge(int p,int q){
if(!p||!q) return p+q;
if(val[p].first>val[q].first) swap(p,q);
ch[p][1]=merge(ch[p][1],q);
if(dis[ch[p][0]]<dis[ch[p][1]]) swap(ch[p][0],ch[p][1]);
dis[p]=dis[ch[p][1]]+1;
return p;
}
};
可持久化左偏树
template<int N,class T> struct leftree{
int ch[N+10][2],dis[N+10],tot; T val[N+10];
leftree():tot(0){dis[0]=-1;}
int newnode(T x){int p=++tot;return val[p]=x,ch[p][0]=ch[p][1]=0,dis[p]=0,p;}
int merge(int p,int q){
if(!p||!q) return p+q;
if(val[p].first-val[q].first>=eps) swap(p,q);
int u=newnode(val[p]);
ch[u][0]=ch[p][0],ch[u][1]=merge(ch[p][1],q);
if(dis[ch[u][0]]<dis[ch[u][1]]) swap(ch[u][0],ch[u][1]);
dis[u]=dis[ch[u][1]]+1;
return u;
}
};
左偏树可以支持全局做一些操作(全局加),和平衡树一样下传标记,记得删除节点的时候下放懒标记。
\(k\) 短路
\(k\) 短路问题输入一个有向带权图和整数 \(s,t,k\),要求找到这张图上 \(s\) 到 \(t\) 的边权和第 \(k\) 小的路径。使用可持久化可并堆解决,时间复杂度为 \(O((n+m)\log n+k\log k)\)。
考虑建出以 \(t\) 为根的反图的最短路树,满足每个点到 \(t\) 的树上路径是原图的(一条)最短路。考虑任意一条从 \(s\) 出发到 \(t\) 的路径,总是可以拆分成若干个形如 树上的儿子到祖先的路径 加上 一条非树边 的拼接。考虑假设现在只有一条非树边在里面,叫做 \((u,v,w)\),那么这条路径的长度是 \(dis_1-dis_u+w+dis_v\),如果还有一条边 \((u',v',w')\) 在 \(v\) 的祖先上,那么就是 \(dis_1\boxed{-dis_u+w+dis_v}\boxed{-dis_{u'}+w'+dis_{v'}}\),也就是说一条边给路径长度的贡献是固定的,不妨叫做 \(\Delta e=dis_v-dis_u+w\) 好了。这里的 \(\Delta e\) 应该是个非负数。
考虑开始进行一波优先队列 bfs,我们对着一个状态,这个状态记录最后一条非树边走到了哪个点 \(u\)(或者 \(s\),如果没有走过)还有当前路径长度 \(cur\)(算上 \(u\) 到 \(t\) 在树上那一段),那我就是爬 \(u\) 在树上的祖先,然后找一条出边 \((f,v,w)\) 出去,转移到 \((cur+\Delta e,v)\)。然后这个就是暴力,我们只要从优先队列里拿出 \(k\) 个状态,我们就说最后那一个是 \(k\) 短路。
考虑优化了,我们用左偏树,每个点存一个左偏树表示这个点和它的所有祖先伸出去的边,然后将优先队列状态改成记录左偏树节点编号,每次拿出状态之后,要么用掉这条边出去,要么将左偏树上两个儿子丢进优先队列,以后慢慢决策,反正左偏树是个小根堆,这样做没有问题。进一步的优化,就是把左偏树换成可持久化左偏树,将自己的左偏树和祖先的合并起来。就做完了。
点击查看代码
#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
const double eps=1e-7;
template<class T> using pqueue=priority_queue<T,vector<T>,greater<T>>;
template<int N,int M,class T=int> struct graph{
int head[N+10],nxt[M<<1],cnt;
struct edge{int u,v; T w;} e[M<<1];
graph(){memset(head,cnt=0,sizeof head);}
edge&operator[](int i){return e[i];}
void add(int u,int v,T w=0){e[++cnt]={u,v,w},nxt[cnt]=head[u],head[u]=cnt;}
void link(int u,int v,T w=0){add(u,v,w),add(v,u,w);}
};
template<int N,class T> struct leftree{
int ch[N+10][2],dis[N+10],tot; T val[N+10];
leftree():tot(0){dis[0]=-1;}
int newnode(T x){int p=++tot;return val[p]=x,ch[p][0]=ch[p][1]=0,dis[p]=0,p;}
int merge(int p,int q){
if(!p||!q) return p+q;
if(val[p].first-val[q].first>=eps) swap(p,q);
int u=newnode(val[p]);
ch[u][0]=ch[p][0],ch[u][1]=merge(ch[p][1],q);
if(dis[ch[u][0]]<dis[ch[u][1]]) swap(ch[u][0],ch[u][1]);
dis[u]=dis[ch[u][1]]+1;
return u;
}
};
int n,m;
double E,dis[5010];
graph<5010,1<<18,double> g,r;
void dijkstra(int s){
pqueue<pair<double,int>> q;
static bool vis[5010];
fill(dis+1,dis+n+1,1e12);
fill(vis+1,vis+n+1,0);
for(q.push({dis[s]=0,s});!q.empty();){
int u=q.top().second; q.pop();
if(vis[u]) continue; else vis[u]=1;
for(int i=r.head[u];i;i=r.nxt[i]){
int v=r[i].v; double w=r[i].w;
if(dis[v]-dis[u]-w>=eps) q.push({dis[v]=dis[u]+w,v});
}
}
}
int root[5010];
leftree<18<<18,pair<double,int>> t;
bool ontree[1<<18];
bool visit[5010];
void dfs(int u){
visit[u]=1;
for(int i=r.head[u];i;i=r.nxt[i]){
int v=r[i].v; double w=r[i].w;
if(!visit[v]&&fabs(dis[v]-dis[u]-w)<eps) dfs(v),ontree[i]=1;
}
}
void pushdown(int u){
for(int i=r.head[u];i;i=r.nxt[i]){
int v=r[i].v; if(!ontree[i]) continue;
root[v]=t.merge(root[v],root[u]);
pushdown(v);
}
}
int main(){
// #ifdef LOCAL
// freopen("input.in","r",stdin);
// #endif
scanf("%d%d%lf",&n,&m,&E);
for(int i=1;i<=m;i++){
int u,v; double w;
scanf("%d%d%lf",&u,&v,&w);
g.add(u,v,w),r.add(v,u,w);
}
dijkstra(n),dfs(n);
for(int u=1;u<n;u++){
if(!visit[u]) continue;
for(int i=g.head[u];i;i=g.nxt[i]){
int v=g[i].v; double w=g[i].w;
if(ontree[i]) continue;
root[u]=t.merge(
root[u],
t.newnode({w-dis[u]+dis[v],v})
);
}
}
pushdown(n);
pqueue<pair<double,int>> q;
int ans=0;
auto update=[&](double w)->bool{
if(E>=w) return E-=w,ans++,1;
else return 0;
debug("E=%.2lf,ans=%d\n",E,ans);
};
if(root[1]) q.push({dis[1]+t.val[root[1]].first,root[1]});
if(update(dis[1])) while(!q.empty()&&E>=eps){
double w=q.top().first; int u=q.top().second;
debug("[%.2lf,%d]\n",w,u);
q.pop();
if(!update(w)) break;
auto ins=[&](double del,int v){if(v) q.push({w+del,v});};
ins(t.val[t.ch[u][0]].first-t.val[u].first,t.ch[u][0]);
ins(t.val[t.ch[u][1]].first-t.val[u].first,t.ch[u][1]);
int v=t.val[u].second;
ins(t.val[root[v]].first,root[v]);
}
printf("%d\n",ans);
return 0;
}
朱刘算法求最小树形图
为了解决最小树形图,问题我们有如下做法:(抄的题解,是父亲指向儿子的)
- 考虑树形图的性质,每个点都有唯一的入边(根节点除外,以后讨论入边,都不考虑根节点),于是我们提出这样一个算法,强制选择每个点边权最小的入边,这样如果不存在环,我们肯定得到了最小的树形图。
- 考虑如何处理环,有一条性质,因为这个环是由最小的入边所形成的环,因此存在一棵最小树形图,只缺少了环上的一条边,而且缺少的这条边所指向的点的入边必在该棵最小树形图上。
- 我们想要这个环缩成一个点 \(cnt\),而且要表现环上的每条边选与不选,对于进入环上的每条边 \((u,v,w)\),\(v\) 为环上的点,\(u\) 非环上的点,令 \(w−=in[v]\)(\(in[v]\) 是 \(v\) 现在的最小入边),然后 \(v=cnt\),答案强制选上环上的边权,然后删除环上所有的内部连边,把这个环缩成一个点,递归进行。
- 每次形成一个环会至少少一个点,时间复杂度 \(O(nm)\)。
这就差不多是一个反悔贪心。
tarjan 优化朱刘算法
复杂度的瓶颈在于缩环和维护 \(in_v\),用左偏树(可并堆)维护。
- 枚举原图上的边 \((pre_u,u,in_u)\),加入图中。
- 如果此时图中有有向环,找出这个环。
- 对每个点维护左偏树表示入边权值。合并左偏树的时候,先是计算贡献,然后点 \(u\) 的左偏树的权值集体减 \(in_u\),合并环上左偏树到一个新的点上。
点击查看代码
输入要求内向树形图,swap(u,v)
之后变成每个点只有一条出边(根没有),跑上面的缩环算法,最后看一下是否除了根其他点都有一条出边,那么选他就是了。
#include <queue>
#include <numeric>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr,##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
template<int N> struct dsu{
int fa[N+10],siz[N+10],cnt;
explicit dsu(int n=N):cnt(n){iota(fa+1,fa+n+1,1),fill(siz+1,siz+n+1,1);}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void merge(int x,int y){if(x=find(x),y=find(y),x!=y) cnt--,fa[y]=x,siz[x]+=siz[y];}
};
template<int N> struct leftree{
int ch[N+10][2],dist[N+10],cnt;
pair<LL,int> val[N+10]; LL tag[N+10];
leftree():cnt(0){dist[0]=-1;}
int newnode(pair<LL,int> v){
int p=++cnt;
val[p]=v,tag[p]=0;
ch[p][0]=ch[p][1]=0,dist[p]=0;
return p;
}
void spread(int p,LL k){val[p].first+=k,tag[p]+=k;}
void pushdown(int p){spread(ch[p][0],tag[p]),spread(ch[p][1],tag[p]),tag[p]=0;}
int merge(int p,int q){
if(!p||!q) return p+q;
pushdown(p),pushdown(q);
if(val[p]>val[q]) swap(p,q);
ch[p][1]=merge(ch[p][1],q);
if(dist[ch[p][0]]<dist[ch[p][1]]) swap(ch[p][0],ch[p][1]);
dist[p]=dist[ch[p][1]]+1;
return p;
}
};
int n,m,k,root[1<<18],rt;
leftree<1<<18> t;
pair<LL,int> to[1<<18];
dsu<1<<18> cyc,idp;
int solve(){
int ans=0;
auto top=[&](int x){return t.val[x];};
auto pop=[&](int&p){t.pushdown(p),p=t.merge(t.ch[p][0],t.ch[p][1]);};
auto getto=[&](int x){
auto res=top(root[x]);
while(root[x]&&idp.find(res.second)==idp.find(x)){
pop(root[x]);
res=top(root[x]);
}
return root[x]?res:make_pair(0ll,0);
};
queue<int> q;
for(int i=1;i<=n;i++) to[i]=getto(i);
for(int i=1;i<=n;i++) if(to[i].second) q.push(i);
int cnt=n;
while(!q.empty()){
int u=q.front(),v=to[u].second; q.pop();
ans+=to[u].first;
vector<int> cycle;
if(cyc.find(u)==cyc.find(v)){
assert(idp.find(u)==u);
cycle.push_back(u);
for(int p=idp.find(to[u].second);p!=u;p=idp.find(to[p].second)){
cycle.push_back(p);
}
}else{cyc.merge(v,u);continue;}
idp.siz[++cnt]=0;
for(int p:cycle){
idp.merge(cnt,p),cyc.merge(cnt,p);
t.spread(root[p],-to[p].first);
root[cnt]=t.merge(root[cnt],root[p]);
}
to[cnt]=getto(cnt);
if(to[cnt].second) q.push(cnt);
}
for(int i=1;i<=cnt;i++) if(idp.find(i)==i&&idp.find(i)!=idp.find(rt)&&!to[i].second) return -1;
return ans;
}
int main(){
scanf("%d%d%d",&n,&m,&rt);
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w); swap(u,v);
if(u!=rt) root[u]=t.merge(root[u],t.newnode({w,v}));
}
printf("%d\n",solve());
return 0;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/template-leftree.html