圆方树是什么呢?
我们先不管仙人掌,来看看一般图的圆方树。
众所周知,
T
a
r
j
a
n
Tarjan
Tarjan算法可以在强连通分量,点双和边双三个地方使用。
有向图是强联通,无向图是双联通。
我们使用
T
a
r
j
a
n
Tarjan
Tarjan算法很多时候都是为了将图简化以达到目的。
S
c
c
Scc
Scc缩点之后会形成一张
D
A
G
DAG
DAG图,用
T
o
p
S
o
r
t
Top\ Sort
Top Sort可以解决许多问题。
边双缩点之后就直接形成了一棵树。
只有点双与众不同,点双中的割点是属于多个块的,所以emmm~~~
接下来就要用到圆方树了!
(借了一张图,读书人的事……2333)
简单来说,就是对原图的每一个点双分量新建一个方点,然后将原有分量中的点连向它。
这样就避免了割点归属的争论了,完美的表现了割点属于多个块的性质(搁置争议共同开发)。
emm,说完了。
1.无论取哪个点为根,圆方树的形态是一样的
2.一定是个森林
3.每个点双有唯一的方点
4.圆点方点相间分布,相同点不相邻
具体的性质各位可以参考一下UOJ
接下来就是简单的例题时间了。
【BJOI2013】压力
Description
如今,路由器和交换机构建起了互联网的骨架。处在互联网的骨干位置的核心路由器典型的要处理100Gbit/s的网络流量。他们每天都生活在巨大的压力之下。
小强建立了一个模型。这世界上有N个网络设备,他们之间有M个双向的链接。这个世界是连通的。在一段时间里,有Q个数据包要从一个网络设备发送到另一个网络设备。
一个网络设备承受的压力有多大呢?很显然,这取决于Q个数据包各自走的路径。不过,某些数据包无论走什么路径都不可避免的要通过某些网络设备。
你要计算:对每个网络设备,必须通过(包括起点、终点)他的数据包有多少个?
Input
第一行包含3个由空格隔开的正整数N,M,Q。
接下来M行,每行两个整数u,v,表示第u个网络设备(从1开始编号)和第v个网络设备之间有一个链接。u不会等于v。两个网络设备之间可能有多个链接。
接下来Q行,每行两个整数p,q,表示第p个网络设备向第q个网络设备发送了一个数据包。p不会等于q。
Output
输出N行,每行1个整数,表示必须通过某个网络设备的数据包的数量。
Sample Input
4 4 2
1 2
1 3
2 3
1 4
4 2
4 3
Sample Output
2
1
1
2
Hint
【数据范围与约定】
样例解释:
设备1、2、3之间两两有链接,4只和1有链接。4想向2和3各发送一个数据包。显然,这两个数据包必须要经过它的起点、终点和1。
数据规模:
对于40%的数据,1<=N,M,Q<=2000
对于60%的数据,1<=N,M,Q<=40000
对于100%的数据,1<=N<=100000,1<=M,Q<=200000
我们知道,在一个点双联通分量中,任意两点之间都有点不重复的路径。
所以之间建出圆方树,然后两点之间的链上圆点(也就是割点)就是一定会经过的点。
树上差分即可。
有趣的是,我的差分方式有点毒瘤,仿照树剖得出Dfs序之后在序列上差分。
这样就没有求Lca的过程了,时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=200005;
inline int read() {
char c; int rec=0;
while((c=getchar())<'0'||c>'9');
while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
return rec;
}
int n,m,Q;
struct Branch {int next,to;} branch[Maxn<<1];
int h[Maxn],cnt=0;
inline void add(int x,int y) {
branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
Branch edge[Maxn<<2];
int e[Maxn<<1],idx=0;
inline void Add(int x,int y) {
edge[++idx].to=y; edge[idx].next=e[x]; e[x]=idx; return ;
}
int Bcc,bl[Maxn<<1];
int dfn[Maxn<<1],low[Maxn<<1],ind=0;
int S[Maxn<<1],Top=0;
void Tarjan(int v) {
dfn[v]=low[v]=++ind; S[++Top]=v;
for(int i=h[v];i;i=branch[i].next) {
int j=branch[i].to;
if(!dfn[j]) {
Tarjan(j); low[v]=min(low[v],low[j]);
if(low[j]>=dfn[v]) {
int p=0; ++Bcc;
Add(Bcc,v); Add(v,Bcc);
while(p!=j) {
p=S[Top--];
Add(Bcc,p); Add(p,Bcc);
}
}
}
else low[v]=min(low[v],dfn[j]);
} return ;
}
int size[Maxn<<1],deep[Maxn<<1];
int top[Maxn<<1],fa[Maxn<<1],son[Maxn<<1];
int id[Maxn],tnt=0;
void Dfs1(int v,int pre,int dep) {
size[v]=1; fa[v]=pre; deep[v]=dep;
for(int i=e[v];i;i=edge[i].next) {
int j=edge[i].to;
if(j==pre) continue;
Dfs1(j,v,dep+1); size[v]+=size[j];
if(size[son[v]]<size[j]) son[v]=j;
} return ;
}
void Dfs2(int v,int T) {
top[v]=T; id[v]=++tnt;
if(son[v]) Dfs2(son[v],T);
for(int i=e[v];i;i=edge[i].next) {
int j=edge[i].to;
if(j!=fa[v]&&j!=son[v]) Dfs2(j,j);
} return ;
}
int sum[Maxn<<1];
int main() {
n=read(); m=read(); Q=read();
for(int i=1;i<=m;++i) {
int x=read(),y=read();
add(x,y); add(y,x);
}
Bcc=n; Tarjan(1);
Dfs1(1,0,1); Dfs2(1,1);
for(int i=1;i<=Q;++i) {
int x=read(),y=read();
while(top[x]!=top[y]) {
if(deep[top[x]]<deep[top[y]]) swap(x,y);
++sum[id[top[x]]]; --sum[id[x]+1];
x=fa[top[x]];
}
if(deep[x]<deep[y]) swap(x,y);
++sum[id[y]]; --sum[id[x]+1];
}
for(int i=1;i<=Bcc;++i) sum[i]+=sum[i-1];
for(int i=1;i<=n;++i) cout<<sum[id[i]]<<'\n';
return 0;
}
圆方树显然可以解决一些带修改的问题。
这里我们要注意一下圆方树的维护。
显然,如果没修改一次圆点就修改所有与之相连的方点的话,在菊花图面前会死的很惨。
于是我们随便拿一个圆点当作圆方树的根,所有的方点维护的信息都不包括父亲节点(当然是一个圆点)。 当我们访问到当前联通块时特判后加上父亲圆点的贡献即可。
题目链接
题目大意:给出一张无向图,然后每次询问两点之间简单路径的最小值,支持修改点权。
首先按照前面的说法建好圆方树,然后就是维护链上的最小值。同时,用multiset来维护方点的最小值(相当于一个支持删除的小根堆)。配合树链剖分+线段树就解决问题了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;
const int Maxn=200005;
inline int read() {
char c; int rec=0;
while((c=getchar())<'0'||c>'9');
while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
return rec;
}
int n,m,Q,val[Maxn];
struct Graph {
struct Branch {int next,to;} branch[Maxn<<1];
int h[Maxn],cnt;
inline void add(int x,int y) {
branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
} G,T;
int Bcc,dfn[Maxn],low[Maxn],idx,S[Maxn],Top;
void Tarjan(int v) {
dfn[v]=low[v]=++idx; S[++Top]=v;
for(int i=G.h[v];i;i=G.branch[i].next) {
int j=G.branch[i].to;
if(!dfn[j]) {
Tarjan(j); low[v]=min(low[v],low[j]);
if(low[j]>=dfn[v]) {
int p=0; ++Bcc;
T.add(Bcc,v); T.add(v,Bcc);
while(p!=j) {
p=S[Top--];
T.add(Bcc,p); T.add(p,Bcc);
}
}
}
else low[v]=min(low[v],dfn[j]);
} return ;
}
multiset<int> U[Maxn<<1];
int D,Minn[Maxn<<3];
int size[Maxn<<1],deep[Maxn<<1];
int top[Maxn<<1],fa[Maxn<<1],son[Maxn<<1];
int id[Maxn<<1],ind;
void Dfs1(int v,int pre,int dep) {
if(v<=n) U[v].insert(val[v]);
size[v]=1; fa[v]=pre; deep[v]=dep;
for(int i=T.h[v];i;i=T.branch[i].next) {
int j=T.branch[i].to;
if(j==pre) continue;
Dfs1(j,v,dep+1); size[v]+=size[j];
if(size[son[v]]<size[j]) son[v]=j;
if(v>n) U[v].insert(val[j]);
} return ;
}
void Dfs2(int v,int TOP) {
top[v]=TOP; id[v]=++ind;
if(son[v]) Dfs2(son[v],TOP);
for(int i=T.h[v];i;i=T.branch[i].next) {
int j=T.branch[i].to;
if(j!=fa[v]&&j!=son[v]) Dfs2(j,j);
} return ;
}
inline int Ask(int L,int R) {
int rec=0x3f3f3f3f;
for(int s=L+D-1,t=R+D+1;s^t^1;s>>=1,t>>=1) {
if(~s&1) rec=min(rec,Minn[s^1]);
if(t&1) rec=min(rec,Minn[t^1]);
} return rec;
}
int main() {
n=read(); m=read(); Q=read();
for(int i=1;i<=n;++i) val[i]=read();
for(int i=1;i<=m;++i) {
int x=read(),y=read();
G.add(x,y); G.add(y,x);
}
char ch;
Bcc=n; Tarjan(1);
Dfs1(1,0,1); Dfs2(1,1);
D=1; while(D<=Bcc) D<<=1;
memset(Minn,0x3f,sizeof(Minn));
for(int i=1;i<=Bcc;++i) Minn[D+id[i]]=*U[i].begin();
for(int i=D;i;--i) Minn[i]=min(Minn[i<<1],Minn[i<<1|1]);
for(int i=1;i<=Q;++i) {
while((ch=getchar())!='A'&&ch!='C');
int x=read(),y=read();
if(ch=='A') {
int Ans=0x3f3f3f3f;
while(top[x]!=top[y]) {
if(deep[top[x]]<deep[top[y]]) swap(x,y);
Ans=min(Ans,Ask(id[top[x]],id[x]));
x=fa[top[x]];
}
if(deep[x]<deep[y]) swap(x,y);
Ans=min(Ans,Ask(id[y],id[x]));
if(y>n) Ans=min(Ans,val[fa[y]]);
cout<<Ans<<'\n';
}
else {
int p=fa[x];
if(p) {
U[p].erase(U[p].find(val[x])); U[p].insert(y);
int v=id[p]+D; Minn[v]=*U[p].begin(); v>>=1;
while(v) Minn[v]=min(Minn[v<<1],Minn[v<<1|1]),v>>=1;
}
val[x]=y;
int v=id[x]+D; Minn[v]=y; v>>=1;
while(v) Minn[v]=min(Minn[v<<1],Minn[v<<1|1]),v>>=1;
}
}
return 0;
}
除了可以辅助数据结构来暴力维护图上信息之外,圆方树还可以将图上的
d
p
dp
dp转化为树上的
d
p
dp
dp。
LOJ 【APIO2018】铁人两项
简单来说,就是找原图中有多少在同一简单路上的三元组。
一种统计方法是枚举s和f,然后计算从s到f的点不重复路径中可以经过的点的个数。
设每个圆点的权值为−1,每个方点的权值为点双大小,那么选法就是两点路径的权值和吧。
O
(
n
2
)
O(n^2)
O(n2)任然不行,所以我们考虑计算一下每个点的贡献。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=200005,Maxm=400005;
inline int read() {
char c; int rec=0;
while((c=getchar())<'0'||c>'9');
while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
return rec;
}
int n,m;
long long ans;
struct Graph {
struct Branch {int next,to;} branch[Maxm<<1];
int h[Maxn<<1],cnt;
inline void add(int x,int y) {
branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
} G,T;
int size[Maxn<<1],tot;
int Bcc,dfn[Maxn],low[Maxn],S[Maxn],w[Maxn],idx,num;
void Tarjan(int v) {
dfn[v]=low[v]=++idx; S[++num]=v; w[v]=-1;
for(int i=G.h[v];i;i=G.branch[i].next) {
int j=G.branch[i].to;
if(!dfn[j]) {
Tarjan(j); low[v]=min(low[v],low[j]);
if(low[j]>=dfn[v]) {
int p=0; ++Bcc; ++w[Bcc];
T.add(Bcc,v); T.add(v,Bcc);
while(p!=j)
p=S[num--],++w[Bcc],T.add(Bcc,p),T.add(p,Bcc);
}
}
else low[v]=min(low[v],dfn[j]);
} return ;
}
void Getsize(int v,int pre) {
size[v]=v<=n;
for(int i=T.h[v];i;i=T.branch[i].next) {
int j=T.branch[i].to;
if(j==pre) continue;
Getsize(j,v); size[v]+=size[j];
} return ;
}
void Sov(int v,int pre) {
int opt=v<=n;
for(int i=T.h[v];i;i=T.branch[i].next) {
int j=T.branch[i].to;
if(j==pre) continue;
Sov(j,v); ans+=1ll*opt*w[v]*size[j]; opt+=size[j];
}
ans+=1ll*size[v]*(tot-size[v])*w[v];
return ;
}
int main() {
n=read(); m=read();
for(int i=1;i<=m;++i) {
int x=read(),y=read();
G.add(x,y); G.add(y,x);
}
Bcc=n;
for(int i=1;i<=n;++i)
if(!dfn[i])
Tarjan(i),Getsize(i,0),tot=size[i],Sov(i,0);
cout<<(ans<<1);
return 0;
}