圆方树
说一下圆方树的定义,大多数情况下,当我们需要对一个复杂图化简为一个更易于操作的结构,就用到了圆方树.
先说圆方树如何建立.
首先跑一遍tarjan,在过程中找点双,每个点双建立一个方点,与所有圆点相连,这样将这个图转化为圆方树.
但并非所有的圆方树都通过直接找点双构成,简单点说,圆方树的建立方式因题而异.
虽说如此,但这种圆方树是最实用的一种,它满足很多性质,比方说整棵树必定是圆点与方点交替连接.
代码实现如下:
int n,m,ti,cnt,top,low[N],dfn[N],st[N];
ve ed[N],t[N];
void tarjan(int now){
dfn[now]=low[now]=++ti;
st[++top]=now;
++num;
for(auto i:ed[now]){
if(!dfn[i]){
tarjan(i);
low[now]=min(low[i],low[now]);
if(low[i]==dfn[now]){
t[now].ps(++cnt);
t[cnt].ps(now);
w[cnt]=1;
int x;
do{
x=st[top--];
t[cnt].ps(x);
t[x].ps(cnt);
++w[cnt];
}while(x!=i);
}
}else low[now]=min(low[now],dfn[i]);
}
}
这里的经典例题是铁人两项,属于转化成圆方树在上面做容斥统计答案的类型.
具体思路是先转化图变为圆方树,然后对树的每一个节点看以其作为中转点c能够造成的答案统计.
圆方树两种点,一种圆点,一种方点,方点由点双变换而来,考虑点双中的每两个点之间都有两个以上的简单路径.
而方点统计的出度大小就是点双本身的大小,在这个点双内部,所有的点均可被另一个点替代作为中转点c.
所以说我们在统计答案时,就将2×(方点出度×任取两个子树sz乘积+方点出度×方点为根树sz×除该树外节点sz)作为答案.
(×2是为了统计节点相同而s,f对调的情况.)
这个可以通过dp(dfs)来解决.
但这样统计造成了在树上的圆点对自身答案的重复统计.
如果只考虑圆点的答案统计,就是它的子树的所有节点个数×除了它隶属的树之外的节点数.
现在它被一个方点记入统计,所有的答案都重复了它所属于的点一遍.
(在任取两个子树sz乘积那一步中多统计了它与其它点双内点的答案,
另外一个是多统计了它的子树与除了和它在一个点双内的其它点答案)
所以在出来的时候需要减去该节点被重复统计的步数,这里的写法比较巧,将圆点统计时置系数为-1,在这种前提下节点的答案不重不漏.
注意图可能不连通,所以每一个未访问的节点都来一次tarjan,单独dfs统计答案 .
#include<bits/stdc++.h>
typedef long long ll;
#define qr qr()
#define ve vector<int>
#define pa pair<int,int>
#define ps push_back
using namespace std;
inline ll qr{
ll x=0;char ch=getchar();bool f=0;
while(ch>57||ch<48)f=(ch=='-')?1:0,ch=getchar();
while(ch>=48&&ch<=57)x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=2e6+200;
int n,m,ti,cnt,low[N],top,sz[N],w[N],dfn[N],st[N],num;
ll ans;
ve ed[N],t[N];
void tarjan(int now){
dfn[now]=low[now]=++ti;
st[++top]=now;
++num;
for(auto i:ed[now]){
if(!dfn[i]){
tarjan(i);
low[now]=min(low[i],low[now]);
if(low[i]==dfn[now]){
t[now].ps(++cnt);
t[cnt].ps(now);
w[cnt]=1;
int x;
do{
x=st[top--];
t[cnt].ps(x);
t[x].ps(cnt);
++w[cnt];
}while(x!=i);
}
}else low[now]=min(low[now],dfn[i]);
}
}
void dfs(int now,int fa){
sz[now]=(now<=n);
for(int i:t[now])
if(i!=fa){
dfs(i,now);
ans+=2ll*w[now]*sz[now]*sz[i];
sz[now]+=sz[i];
}
ans+=2ll*w[now]*sz[now]*(num-sz[now]);//这一步对于方点的父节点来说,重复统计了父节点选入其本身作c,又作f/s的答案.
}
void init(){
n=qr;cnt=n;m=qr;
int x,y;
for(int i=1;i<=n;++i)w[i]=-1;
for(int i=1;i<=m;++i){
x=qr,y=qr;
ed[x].ps(y);ed[y].ps(x);
}// tarjan(1,0);
for(int i=1;i<=n;++i)
if(!dfn[i]){
num=0;
tarjan(i);--top;
dfs(i,0);
}
printf("%lld",ans);
}
int main(){
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
init();
return 0;
}
tourists.
to be continue.