Problem
给定一个N(≤100000)个点、M(≤200000)条边的连通图,以及Q(≤200000)个点对(u,v)。求删除每个点后分别会有多少个点对不连通。
Solution
- 考虑求出每个点对(u,v)的(搜索树)路径中,会造成影响的点。
- 这看似就是u到v路径上的割点,实则不是。例如下图:
- 个中4到2有一条返祖边。
- 设有个点对为(2,5),该路径上的点3为割点。因为删除点3后,以点7为根的子树“遂与外人间隔”。但它并不会影响到点2和点5的连通性。
- 先思考一下割点的定义。
- 删去某个割点后,会使全图的连通块数量增加。换句话说,会让它的某棵子树无法到达上面的连通块。
- 而我们又知道,割点是被包含在2个或以上的点双里面的。确切地说,点双就是以割点为分界的;我们由某个点双到达另一个点双,也必须要经过某个割点。
- 譬如对于下图:
- 一个红圈内包含一个点双,故点双有3个:{1,2},{1,3,4},{2,5,6}。个中,割点有2个:1、2。
- 可以考虑将每个点双缩成一个点。准确地说,对于每个点双,我们都建一个方点(虚点);每个方点连向该点双内的所有点,后者成为圆点。换句话说,圆点就是原图中的点;方点就是新建的点。
- 如下图所示:
- 由上图可知,方点由割点连接。同时,整个图被转化成了一棵树。这棵树就叫圆方树。
- 在圆方树中,u到v的路径对应原图中u到v必须经过的点。
- 回到题目,对于点对(u,v),我们应在圆方树上的u到v的路径上给每个点的答案+1。因为这条路径其实包括了:1)起点u;2)终点v;3)必须经过的点双对应的方点;4)所有会影响到(u,v)连通性的割点。其中,1)2)4)都是我们必须维护的。
- 显然,不能 O(n) O ( n ) 暴力维护。
- 考虑上图:圆方树以点1为根;记w=lca(u,v)。我们现在要使u到v的路径答案+1。
- 可以考虑树上差分。在点u打个+1标记,点v打个+1标记;点w打个-1标记,点father[w]打个-1标记。
- 这样, O(n) O ( n ) dfs一遍即可求得答案。
- 时间复杂度: O(m+q+nα(n))(tarjanLCA)or O(m+q+nlog2n)(倍增LCA) O ( m + q + n α ( n ) ) ( t a r j a n L C A ) o r O ( m + q + n l o g 2 n ) ( 倍 增 L C A ) 。
Code
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,a,b) for(i=a;i<=b;i++)
#define repe(i,x) for (edge *i=x; i; i=i->ne)
using namespace std;
const int N=2e5+1,M=N<<2;
int i,j,n,m,Q,u,v;
struct edge
{
int v;
edge *ne;
}e[M],*tp=e,a[M],*tot=a,*final[N],*fin[N];
void read(int&x)
{
char ch=getchar(); x=0;
for(;!isdigit(ch);ch=getchar());
for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}
inline void link(int u,int v)
{
*tp=(edge){v,final[u]},final[u]=tp++;
*tp=(edge){u,final[v]},final[v]=tp++;
}
inline void Link(int u,int v)
{
*tot=(edge){v,fin[u]},fin[u]=tot++;
*tot=(edge){u,fin[v]},fin[v]=tot++;
}
int tim,dfn[N],low[N],fat[N],top,sq;
edge *st[N];
void tarjan(int x)
{
dfn[x]=low[x]=++tim;
repe(i,final[x])
{
int y=i->v;
if(y!=fat[x])
if(!dfn[y])
{
fat[y]=x;
st[++top]=i; tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
Link(n+(++sq),x);
do
Link(n+sq,st[top]->v);
while(st[top--]!=i);
}
}
else low[x]=min(low[x],dfn[y]);
}
}
int p[N][2],sum[N],fa[N],w;
bool bz[N];
vector<int>ve[N][2];
vector<int>::iterator it;
int gef(int x)
{
return fa[x]?fa[x]=gef(fa[x]):x;
}
void dfs(int x)
{
int y;
fo(y,0,1)
for(it=ve[x][y].begin();it!=ve[x][y].end();it++)
if(!bz[*it])
bz[*it]=1;
else w=gef(p[*it][!y]), sum[w]--, sum[fat[w]]--;
repe(i,fin[x])
{
y=i->v;
if(y!=fat[x]) fat[y]=x, dfs(y), sum[x]+=sum[y];
}
fa[x]=fat[x];
}
int main()
{
read(n);read(m);read(Q);
fo(i,1,m) read(u), read(v), link(u,v);
fo(i,1,n) if(!dfn[i]) tarjan(i);
fo(i,1,Q)
fo(j,0,1)
read(p[i][j]), sum[p[i][j]]++, ve[p[i][j]][j].push_back(i);
memset(fat,0,sizeof fat);
dfs(1);
fo(i,1,n) printf("%d\n",sum[i]);
}