题意:
给一张无向图,q次操作,每次在指定两点之间添加一条路径,
问添加上这条路径之后图中有多少条割边。
思路:
首先我们知道,v-dcc缩点后图(就是一棵树)中的各点之间通过割边连接,且包含原图
中所有割边,当我们在原图中的某两点之间添加一条路径之后,对应v-dcc缩点后图中 的两
个点之间的路径上的边将不在是割边,其路径长度可以用LCA的方法来求。确定这些边不是
割边后,需要作出标记,然后标记一下就可以了(如果朴素的由子节点向LCA走的话总的时
间复杂度约为M+Q*N),(tarjan算法的复杂度差不多N+M)。因为被标记过的边可以不再
被访问。所以我们可以用并查集优化,在从子节点向LCA走的时候,每标记一条边就将这条
边的子节点与与其父节点合并。这样我们在再次标记该点之后路径时可以先得到该点所属于
的v-dcc,然后并查集find(对应v-dcc的编号)就可以跳过那些已经被标记的边,直接到达还
没有被标记的边的起点(这里注意判断跳多了越过LCA或者偏离向LCA走的情况)。
WA了几次:主要原因有这些小算法不熟练,理解不透彻,组合起来的时候量很多,容易混,
其次有些地方的代码操作是重复的,如果处理不好(多写太多重复代码),会TLE。再就是从
子节点往LCA走的过程容易出错了,因为涉及到要用并查集跳过许多边,所以容易跳过了,
其次一个点连接着多条边,但怎么找出其连接父节点的那条边。
自己的代码就是把那些小算法拼接了一下,网上题解相比做了如下优化:①不再单独存储
缩点后的图(省出缩点的代码),直接在求v-dcc时用并查集将同一联通块内的点连接起来。
就相当于他们之间的边都已经被标记为不是割边了。②查询时,一边求LCA一边合并不再是
桥的那些点。(因为本身求LCA的过程本身就需要从两个子节点往上走一直到LCA,这里如
果用倍增,求LCA是快了,但求完之后还要再重复走一次来记录非桥边,得不偿失)。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
大神的该题的真正代码:
(不过有个小问题,他的路径压缩只是优化了查询两点是否在同一双联
通块内,但向lca走的过程没有用路径压缩优化)。
#include
#include
#include
using namespace std;
#define _rep(i,a,b) for(int i=(a);i<=(b);i++)
const int N=1e5+10;
const int M=2e5+10;
int ca,n,m,q,head[N],tot,fa[N],low[N],dfn[N],num,cnt,pre[N];
struct Edge{
int v,nx;
}edge[M<<1];
inline void addedge(int u,int v)
{
edge[tot].v=v;
edge[tot].nx=head[u];
head[u]=tot++;
}
int findfa(int x){return fa[x]==x?x:fa[x]=findfa(fa[x]);}
bool merge(int x,int y)
{
int fx=findfa(x);
int fy=findfa(y);
if(fx==fy)return false;
fa[fy]=fx;return true;
}
void tarjan(int u,int f)
{
dfn[u]=low[u]=++num;
int flag=0;
for(int i=head[u];~i;i=edge[i].nx)
{
int v=edge[i].v;
if(v==f&&!flag){flag=1;continue;}
if(dfn[v]==-1)
{
pre[v]=u;
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])cnt++;
else merge(u,v);//求割边的同时处理处理原图中那些非桥边
}
else low[u]=min(low[u],dfn[v]);
}
}
inline int lca(int u,int v)//求LCA的较朴素算法
{
if(findfa(u)==findfa(v))return cnt;//并查集只在这里起作用,下面的移动每天有体现出路径压缩
if(dfn[u]>dfn[v])swap(u,v);
while(dfn[u]
The end;