圆方树
Great_Influence
2018-06-10 10:05:57
圆方树是个好东西,可以将树上的题目出到一般图上~~(就是毒瘤)~~。今年的CCFWC上就新出了一种快速建树方法。圆方树变得比以前不那么深不可测,作用也拓展了。
## 1 定义
#### 1.1 圆方树在仙人掌上的定义
这个还挺简单的。
原来的点都视为圆点。对于每个**简单环**,用一个方点来代替这个环,并且该方点和环上所有的圆点连边。可以简单证明,这样建出来的图一定是一棵树。因为它由圆点和方点组成,所以称为圆方树。
#### 1.2 圆方树在一般图上的定义
因为一般图没有仙人掌优秀的性质(即每条边属于且仅属于一个简单环),所以按照上文的定义建出的不一定是一棵树。我们需要修改它的定义。
原来的点都视为圆点。但是此时,**不再是对于简单环**,而是对于每个**边双**,用一个方点代替它,并且该方点和边双内的所有圆点连边。同样可以证明,建出来的图是一棵树。我们同样将它称为圆方树。
另外,为了方便处理,我们将**原图中的一条割边也视为一个点双**。
如下图,左图是原图,右图为左图的圆方树。(为了方便,右图每个点的标号增大了10)右图中的黑点表示圆点,白点表示方点。
![此处是圆方树](https://cdn.luogu.com.cn/upload/pic/20891.png)
## 2 建树
#### 2.1 描述
主要采用$tarjan$边双魔改来建树。
需要的数组和tarjan边双的一样,但是注意要区分原图的边和圆方树的边。
算法仍然是基于$dfs$序的。对于每个点,求出其$dfs$序$dfn$和通过返祖边最远可达的点标号$low$。并在$dfs$的过程中用栈记录一路上访问的节点。如果某条边$(u,v)$满足$low[v]\ge dfn[u]$,则$u,v$及其中的所有点都属于一个边双。对于该边双新建一个节点代表该边双,将该点的父节点设为$u$(如果没有需求则不需要)且连边,然后将$v$之后的栈内所有元素**(包括$v$)** 全部和该边双连边,父节点为该边双(还是没有需求就不连)。这样就可以在$O(n)$的时间内将圆方树建出。注意,具体每个边双有哪些点**不重要**,可以不记录。
#### 2.2 具体代码实现
```cpp
void tarjan(int u,int ff)
{
dfn[u]=low[u]=++dfc;sta[++tp]=u;
for(register int v=p.head[u];v;v=p.p[v].nxt)
{
if(!dfn[p.p[v].v])
{
tarjan(p.p[v].v,u);
Chkmin(low[u],low[p.p[v].v]);
if(low[p.p[v].v]>=dfn[u])
{
fa[0][++cnt]=u,q.add(u,cnt);
static int las;
do
{
las=sta[tp--];
fa[0][las]=cnt,q.add(cnt,las);
}while(las^p.p[v].v);
}
}else if(p.p[v].v^ff)Chkmin(low[u],dfn[p.p[v].v]);
}
}//注意此处的fa数组是二维的,作用是倍增。一般只需要一维就可以了
//好像代码比tarjan求边双要短
```
## 3 具体作用
主要用于毒瘤题。
树形$dp\to$一般图$dp$
虚树$dp\to$虚圆方树$dp$
树链剖分$\to$圆方树剖分
树分治$\to$圆方树分治
(以下省略一大波毒瘤题)
## 4 例题
#### 4.1 [luogu P4320] 道路相遇
[就是这个](https://www.luogu.org/problemnew/show/P4320)。
分析发现就是求路径必经点数,也就是圆方树上圆点个数。套用倍增求解即可。时间复杂度$O(n+qlogn)$。
代码:
```cpp
#include<bits/stdc++.h>
#define For(i,a,b) for(i=(a);i<=(b);++i)
#define Forward(i,a,b) for(i=(a);i>=(b);--i)
#define Rep(i,a,b) for(register int i=(a),i##end=(b);i<=i##end;++i)
#define Repe(i,a,b) for(register int i=(a),i##end=(b);i>=i##end;--i)
#define Chkmin(a,b) a=a<b?a:b
#define Chkmax(a,b) a=a>b?a:b
using namespace std;
template<typename T>inline void read(T &x){
T s=0,f=1;char k=getchar();
while(!isdigit(k)&&k^'-')k=getchar();
if(!isdigit(k)){f=-1;k=getchar();}
while(isdigit(k)){s=s*10+(k^48);k=getchar();}
x=s*f;
}
void file(void){
#ifndef ONLINE_JUDGE
freopen("water.in","r",stdin);
freopen("water.out","w",stdout);
#endif
}
const int MAXN=1e6+7,MAXM=2e6+7;
static int n,m,Q,e;
struct edge
{
int v,nxt;
};
static struct edges
{
edge p[MAXM];
int head[MAXN];
inline void add(int u,int v){this->p[++e]=(edge){v,head[u]};this->head[u]=e;}
}p,q;
static int dfn[MAXN],low[MAXN],bel[MAXN],cnt,sta[MAXN],tp,dfc,fa[19][MAXN];
void tarjan(int u,int ff)
{
dfn[u]=low[u]=++dfc;sta[++tp]=u;
for(register int v=p.head[u];v;v=p.p[v].nxt)
{
if(!dfn[p.p[v].v])
{
tarjan(p.p[v].v,u);
Chkmin(low[u],low[p.p[v].v]);
if(low[p.p[v].v]>=dfn[u])
{
fa[0][++cnt]=u,q.add(u,cnt);
static int las;
do
{
las=sta[tp--];
fa[0][las]=cnt,q.add(cnt,las);
}while(las^p.p[v].v);
}
}else if(p.p[v].v^ff)Chkmin(low[u],dfn[p.p[v].v]);
}
}
static int sm[21][MAXN],dep[MAXN];
void caldep(int u)
{
dep[u]=dep[fa[0][u]]+1;
for(register int v=q.head[u];v;v=q.p[v].nxt)caldep(q.p[v].v);
}
inline void init()
{
memset(q.head,0,sizeof q.head);
memset(p.head,0,sizeof p.head);
memset(dfn,0,sizeof dfn);
memset(low,0,sizeof low);
memset(bel,0,sizeof bel);
memset(sm,0,sizeof sm);
memset(fa,0,sizeof fa);
read(n);read(m);
static int u,v;
Rep(i,1,m)read(u),read(v),p.add(u,v),p.add(v,u);
e=dfc=tp=0;cnt=n;
tarjan(1,0);
Rep(i,1,n)sm[0][i]=1;
Rep(j,1,19)Rep(i,1,n)
{
sm[j][i]=sm[j-1][i]+sm[j-1][fa[j-1][i]];
fa[j][i]=fa[j-1][fa[j-1][i]];
}
caldep(1);
}
inline int lca(int u,int v)
{
if(dep[u]<dep[v])swap(u,v);
static int ans;ans=0;
Repe(i,19,0)if(dep[u]-dep[v]>=(1<<i))ans+=sm[i][u],u=fa[i][u];
if(u==v)return ans+sm[0][u];
Repe(i,19,0)if(fa[i][u]^fa[i][v])ans+=sm[i][u]+sm[i][v],u=fa[i][u],v=fa[i][v];
return ans+sm[0][u]+sm[0][v]+sm[0][fa[0][u]];
}
void dfs_print(int u)
{
for(register int v=q.head[u];v;v=q.p[v].nxt)
{
printf("%d %d\n",u,q.p[v].v);
dfs_print(q.p[v].v);
}
}
inline void solve()
{
//dfs_print(1);
read(Q);
static int u,v;
Rep(i,1,Q)
{
read(u),read(v);
printf("%d\n",lca(u,v));
}
}
int main(void){
file();
init();
solve();
cerr<<1.0*clock()/CLOCKS_PER_SEC<<endl;
return 0;
}
```
#### 4.2 [SDOI2018]战略游戏
[不会是这个吧](https://www.luogu.org/problemnew/show/P4606)
发现就是上一道题套用虚树$dp$。直接做就可以了。
代码:
```cpp
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
#include<cassert>
#define Rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define Repe(i,a,b) for(register int i=(a);i>=(b);--i)
#define rep(i,a,b) for(register int i=(a);i<(b);++i)
#define pb push_back
#define mx(a,b) (a>b?a:b)
#define mn(a,b) (a<b?a:b)
typedef unsigned long long uint64;
typedef unsigned int uint32;
typedef long long ll;
using namespace std;
namespace IO
{
const uint32 Buffsize=1<<15,Output=1<<24;
static char Ch[Buffsize],*S=Ch,*T=Ch;
inline char getc()
{
return((S==T)&&(T=(S=Ch)+fread(Ch,1,Buffsize,stdin),S==T)?0:*S++);
}
static char Out[Output],*nowps=Out;
inline void flush(){fwrite(Out,1,nowps-Out,stdout);nowps=Out;}
template<typename T>inline void read(T&x)
{
x=0;static char ch;T f=1;
for(ch=getc();!isdigit(ch);ch=getc())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getc())x=x*10+(ch^48);
x*=f;
}
template<typename T>inline void write(T x,char ch='\n')
{
if(!x)*nowps++='0';
if(x<0)*nowps++='-',x=-x;
static uint32 sta[111],tp;
for(tp=0;x;x/=10)sta[++tp]=x%10;
for(;tp;*nowps++=sta[tp--]^48);
*nowps++=ch;
if(nowps-Out>1e7)flush();
}
}
using namespace IO;
void file()
{
#ifndef ONLINE_JUDGE
FILE*DSD=freopen("water.in","r",stdin);
FILE*CSC=freopen("water.out","w",stdout);
#endif
}
inline void Chkmin(int&u,int v){u>v?u=v:0;}
const int MAXN=2e5+7;
static int n,m;
vector<int>ed[MAXN],Ed[MAXN];
static int dfn[MAXN],dfs_clock,low[MAXN],sta[MAXN],tp,e;
void tarjan(int u,int fr)
{
dfn[u]=low[u]=++dfs_clock,sta[++tp]=u;
for(int v:ed[u])if(!dfn[v])
{
tarjan(v,u),Chkmin(low[u],low[v]);
if(low[v]>=dfn[u])
{
register int cur=++e;
Ed[cur].resize(0),Ed[u].pb(cur);
do{Ed[cur].pb(sta[tp--]);}while(sta[tp+1]^v);
}
}
else if(v^fr)Chkmin(low[u],dfn[v]);
}
inline void init()
{
read(n),read(m);
static int u,v;
Rep(i,1,n)dfn[i]=low[i]=0,ed[i].resize(0),Ed[i].resize(0);e=n;
Rep(i,1,m)read(u),read(v),ed[u].pb(v),ed[v].pb(u);
dfs_clock=0,tarjan(1,0);
}
static int dp[MAXN];
namespace LCA
{
static int efn[MAXN],nx[19][MAXN<<1],efs_clock,dep[MAXN];
void efs(int u)
{
nx[0][efn[u]=++efs_clock]=u;
for(int v:Ed[u])
{
dep[v]=dep[u]+1,dp[v]=dp[u]+(v<=n);
efs(v);
nx[0][++efs_clock]=u;
}
}
inline int fixmx(int u,int v){return dep[u]<dep[v]?u:v;}
inline void initial()
{
dep[1]=dp[1]=1,efs_clock=0,efs(1);
int mxlg=31-__builtin_clz(efs_clock);
Rep(j,1,mxlg)Rep(i,1,efs_clock-(1<<j-1)+1)
nx[j][i]=fixmx(nx[j-1][i],nx[j-1][i+(1<<j-1)]);
}
inline int getlca(int u,int v)
{
int l=efn[u],r=efn[v];
if(l>r)swap(l,r);
int k=31-__builtin_clz(r-l+1);
return fixmx(nx[k][l],nx[k][r-(1<<k)+1]);
}
}
using LCA::initial;
using LCA::getlca;
static int Q,k,nd[MAXN<<1];
inline bool cmp(const int&a,const int&b){return LCA::efn[a]<LCA::efn[b];}
inline void solve()
{
initial();
read(Q);
register int ans;
Rep(i,1,Q)
{
read(k),ans=-k;
Rep(j,1,k)read(nd[j]);
sort(nd+1,nd+k+1,cmp);
Rep(j,1,k-1)nd[k+j]=getlca(nd[j],nd[j+1]);
sort(nd+1,nd+k+k,cmp);
k=unique(nd+1,nd+k+k)-nd-1;
tp=0;
Rep(j,1,k)
{
while(tp&&getlca(sta[tp],nd[j])^sta[tp])
ans+=tp>1?dp[sta[tp]]-dp[sta[tp-1]]:0,--tp;
sta[++tp]=nd[j];
}
while(tp)ans+=tp>1?dp[sta[tp]]-dp[sta[tp-1]]:0,--tp;
ans+=sta[1]<=n;
write(ans);
}
}
int main()
{
file();
static int _;read(_);
while(_--)init(),solve();
flush();
return 0;
}
```
#### 4.3 [APIO2018]铁人两项
[点我](https://www.luogu.org/problemnew/show/P4630)
发了题解,具体做法及代码见[这篇博客](https://www.luogu.org/blog/user7035/solution-p4630)。