圆方树
圆方树#
前言#
众所周知,树往往有着许多非常好的性质
圆方树就是一种把一个图变成一棵树的方式
定义#
我们一般在无向图上使用圆方树
先介绍点双连通分量
一个点双连通图的定义:图中任意两点之间都至少有两条不同的路径可互相抵达
一个点双连通分量是一个极大点双连通图
在圆方树中,每个原图中的点对应一个圆点,每个点双对应方点,每个点(圆点)和其对应的点双(方点)连边
上图就分别展示了原图,点双以及对应的圆方树
显然,圆点不和圆点连,方点不和方点连
我们可以在方点处维护有关点双内多条路径的信息
如何建树#
首先我们需要跑一遍 找出每个点双,然后将每个点双代表的方点和点双内的点代表的圆点连边,就建出了一棵圆方树
如下:
inline void tarjan(int x){
dfn[x]=low[x]=++cnt;
S.push(x);
for(auto y:G[x]){
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]==dfn[x]){
++col_cnt;
for(int j=0;j!=y;S.pop()){
j=S.top();
H[col_cnt].pb(j);
H[j].pb(col_cnt);
}
H[col_cnt].pb(x);
H[x].pb(col_cnt);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
习题#
CF487E tourists#
题意:
给定一张图(保证连通),每个点有点权。
现在有两种操作:
-
C a w
:把 的点权改为 -
A a b
:询问从 到 的所有简单路径(不经过重复点)中,点权最小的点的点权。
思路:
在图上搞树剖的操作?
考虑先把图变成一棵树
采用圆方树,然后搞树剖
容易想到把每个方点的权值设为周围所有圆点的最小的权值,搞个 维护一下
然而暴力修改会被卡
利用圆方树是一棵树的性质
我们把每个方点的权值设为其所有儿子节点的最小值,对于每一个方点开一个 维护最值
这样就可以保证复杂度
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define lsp p<<1
#define rsp p<<1|1
#define size pajo
#define int long long
const int N=3e5+5;
const int inf=1e9+5;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int n,m,q,cnt,col_cnt;
int w[N];
int low[N],dfn[N];
int dep[N],f[N],size[N],son[N];
int top[N],val[N];
stack <int> S;
multiset <int> s[N];
vector <int> G[N],H[N];
struct node{
int l,r;
int mn;
}t[N<<2];
node operator + (node l,node r){
return (node){l.l,r.r,min(l.mn,r.mn)};
}
inline void build(int p,int l,int r){
if(l==r){
t[p]={l,r,val[l]};
return;
}
int mid=l+r>>1;
build(lsp,l,mid);
build(rsp,mid+1,r);
t[p]=t[lsp]+t[rsp];
}
inline void update(int p,int x,int k){
if(t[p].l==t[p].r&&t[p].l==x){
t[p].mn=k;
return;
}
int mid=t[p].l+t[p].r>>1;
if(x<=mid) update(lsp,x,k);
else update(rsp,x,k);
t[p]=t[lsp]+t[rsp];
}
inline node query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p];
node res={0,0,inf};
int mid=t[p].l+t[p].r>>1;
if(l<=mid) res=res+query(lsp,l,r);
if(mid<r) res=res+query(rsp,l,r);
return res;
}
inline void dfs1(int x,int fa){
dep[x]=dep[fa]+1,size[x]=1,f[x]=fa;
int maxson=-1;
for(auto y:H[x]){
if(y==fa) continue;
dfs1(y,x);
size[x]+=size[y];
if(size[y]>maxson) maxson=size[y],son[x]=y;
}
}
inline void dfs2(int x,int tp){
dfn[x]=++cnt,top[x]=tp,val[cnt]=w[x];
if(!son[x]) return;
dfs2(son[x],tp);
for(auto y:H[x]){
if(y==f[x]||y==son[x]) continue;
dfs2(y,y);
}
}
inline int Q(int x,int y){
node res=(node){0,0,inf};
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=res+query(1,dfn[top[x]],dfn[x]);
x=f[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
res=res+query(1,dfn[x],dfn[y]);
if(x>n)
res.mn=min(res.mn,w[f[x]]);
return res.mn;
}
inline void tarjan(int x){
dfn[x]=low[x]=++cnt;
S.push(x);
for(auto y:G[x]){
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]==dfn[x]){
++col_cnt;
for(int j=0;j!=y;S.pop()){
j=S.top();
H[col_cnt].pb(j);
H[j].pb(col_cnt);
}
H[col_cnt].pb(x);
H[x].pb(col_cnt);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
signed main(){
n=read(),m=read(),q=read();
col_cnt=n;
for(int i=1;i<=n;++i) w[i]=read();
for(int i=1;i<=m;++i){
int x=read(),y=read();
G[x].pb(y);
G[y].pb(x);
}
tarjan(1);
memset(dfn,0,sizeof(dfn));
cnt=0;
dfs1(1,0);
for(int i=1;i<=n;++i)
if(f[i])
s[f[i]].insert(w[i]);
for(int i=n+1;i<=col_cnt;++i)
w[i]=*s[i].begin();
dfs2(1,1);
build(1,1,col_cnt);
while(q--){
char ch;
cin>>ch;
int x=read(),y=read();
if(ch=='A') cout<<Q(x,y)<<endl;
else{
update(1,dfn[x],y);
if(!f[x]){
w[x]=y;
continue;
}
s[f[x]].erase(s[f[x]].lower_bound(w[x]));
s[f[x]].insert(y);
if(w[f[x]]!=*s[f[x]].begin()){
w[f[x]]=*s[f[x]].begin();
update(1,dfn[f[x]],w[f[x]]);
}
w[x]=y;
}
}
}
P4606 战略游戏#
题意:
给出一个无向图,有 次询问:
每次给出一个点集 (),问有多少个点 满足 且删掉 之后 中的点不全在一个连通分量中。
思路:
先建出一棵圆方树,问题就转变成询问 在圆方树上对应的最小连通子图中的圆点个数减去
画图可知:
我们可以把 中的点按照 从小到大排序,计算排序后相邻两点之间链上圆点的个数之和
这样就可以覆盖整个连通子图
由于每个点被算了两次,最后答案要除以
如果子图中的深度最浅的节点 是圆点,答案还要加上
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
#define pb push_back
#define re register
inline int read(){
re int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int n,m,q;
int dfn[N],low[N],d[N],f[N][18],dep[N],s[N];
int cnt,tp,tot;
vector <int> G[N],H[N];
inline void tarjan(int x){
dfn[x]=low[x]=++cnt;
s[++tp]=x;
for(re int y:G[x]){
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]==dfn[x]){
++tot;
for(re int j=0;j!=y;--tp){
j=s[tp];
H[tot].pb(j);
H[j].pb(tot);
}
H[tot].pb(x);
H[x].pb(tot);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
inline void dfs(int x,int fa){
dfn[x]=++cnt;
dep[x]=dep[fa]+1;
f[x][0]=fa;
d[x]=d[fa]+(x<=n);
for(re int i=1;i<=17;++i)
f[x][i]=f[f[x][i-1]][i-1];
for(re int y:H[x])
if(y!=fa) dfs(y,x);
}
inline int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(re int j=17;j>=0;--j)
if(dep[f[x][j]]>=dep[y]) x=f[x][j];
if(x==y) return x;
for(re int j=17;j>=0;--j)
if(f[x][j]!=f[y][j])
x=f[x][j],y=f[y][j];
return f[x][0];
}
inline void solve(){
n=read(),m=read();
for(re int i=1;i<=n;++i) G[i].clear(),dfn[i]=low[i]=0;
for(re int i=1;i<=2*n;++i) H[i].clear();
for(re int i=1;i<=m;++i){
re int x=read(),y=read();
G[x].pb(y);
G[y].pb(x);
}
cnt=0,tot=n,tp=0;
tarjan(1);
cnt=0;
dfs(1,0);
q=read();
while(q--){
re int S,A[N];
S=read();
re int ans=-2*S;
for(re int i=1;i<=S;++i) A[i]=read();
sort(A+1,A+S+1,[](int i,int j){return dfn[i]<dfn[j];});
for(re int i=1;i<=S;++i){
re int x=A[i],y=A[i%S+1];
ans+=d[x]+d[y]-2*d[lca(x,y)];
}
if(lca(A[1],A[S])<=n) ans+=2;
printf("%d\n",ans/2);
}
}
signed main(){
re int T=read();
while(T--)
solve();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
2020-07-06 浅谈树状数组