目录
include using define 以及constexpr常量预先声明
题目大意:
给定无重边 无自环 的无向图G(N个顶点,M条无权边);
现有Q条询问:给定两点u,v,问u与v之间有几条可达路径,满足路上除了首尾两点外其余点都不重复
解题思路:
1 存在两条及以上不重复路径 位于同一双连通分量
tarjan
下图2 5
2 存在且仅存在一条不重复路径 位于不同双连通分量/共同构成两点一边的BCC
下图2 8 / 6
7
3 不存在可达路径 不连通
并查集/dfs染色
不可达(图中未标出)

代码模块:
include using define 以及constexpr常量预先声明
#include <cstdio>
#include <algorithm>
#include <vector>
#include <stack>
#include <unordered_set>
#include <bitset>
#define ireg register int
#define from first
#define to second
using EDGE = std::pair<int,int>;
static constexpr int NN{5000},MM{10000};
static std::vector<int> G[NN]{
{0,},},/*邻接表*/bcc[NN]{
{0,},}/*点双/块存储*/;
static int dfn[NN]{0}/*时间戳*/,rep[NN]{0}/*并查集代表元*/,
TS = 0,/*timestamp 计时器*/cnt = 0/*块计数*/,
CASES = 0,/*输入组数*/N = 0,M = 0,Q = 0,
u = 0,v = 0;
static std::stack<EDGE> stk/*临时栈*/;
static std::unordered_set<int> bccID[NN]/*每个顶点位于的块编号表*/;
static std::bitset<NN> isCut/*割点标记*/;
主函数读入
int main(void)
{
scanf("%d",&CASES);
while(CASES--)
{
scanf("%d%d",&N,&M);
std::fill_n(rep,N,-1);/*代表元初始化-1 深度1*/
ireg i;
for(i = 0;i < N;++i)G[i].clear(),bccID[i].clear();/*清空图和BCC*/
for(i = 0;i < M;++i)scanf("%d%d",&u,&v),EMore(u,v),EMore(v,u),uni(u,v);/*加边,合并并查集*/
scanf("%d",&Q);
cnt = -1,TS = 0,std::fill_n(dfn,N,0);
while(!stk.empty())stk.pop();/*清空临时栈*/
isCut.reset();/*清空割点标记*/
for(i = 0;i < N;++i)if(!dfn[i])tarjan(i,-1);/*tarjan*/
while(Q--){scanf("%d%d",&u,&v);int ret;/*判断*/
printf("%s\n",(ret = jdg(u,v)) == 0?"0":(ret == 1?"1":"two or more"));
}
}
return 0;
}
并查集(压缩路径、按秩合并/深度)

int _get(int u){return rep[u]<0?u:rep[u] = _get(rep[u]);}/*求代表元 rep初始化-1 代表深度1*/
void uni(int u,int v)
{
int ru = _get(u),rv = _get(v);if(ru == rv)return;
if(std::abs(rep[ru]) <= std::abs(rep[rv]))/*比较深度*/
rep[rv] -= (uint32_t)(rep[ru] == rep[rv]),rep[ru] = rv;
else rep[rv] = ru;
}/*按秩合并*/
加边函数
inline void EMore(int u,int v){G[u].push_back(v),G[v].push_back(u);}/*无向边*/
tarjan
int tarjan(int u,int fa)
{
int lu = dfn[u] = ++TS/*盖戳*/,lv,c = 0;
/*
*lu lv是low最小回溯值
*c是以u为根的搜索树子树个数
*/
for(auto v:G[u])/*遍历邻接边*/
{
auto e = (EDGE){u,v};
if(!dfn[v]){++c;/*子树规模+1*/
stk.push(e);lu = std::min(lu,lv = tarjan(v,u));/*用lowV松弛更新lowU*/
if(lv >= dfn[u])/*割点等价条件*/{
isCut.set(u,(bool)1),/*标记割点*/bcc[++cnt].clear();/*准备收集BCC*/
decltype(e)x{-1,-1};
while(!(x == (EDGE){u,v})){
x = stk.top();stk.pop();
/*尝试收集临时栈中存储的边的两顶点*/
if(!bccID[x.from].count(cnt))
bcc[*(bccID[x.from].insert(cnt)).first].push_back(x.from);
if(!bccID[x.to].count(cnt))
bcc[*(bccID[x.to].insert(cnt)).first].push_back(x.to);
}
}
}
else if(dfn[v] < dfn[u] && !(v == fa))/*返祖边或横插边 更新lowU*/
stk.push(e),lu = std::min(lu,dfn[v]);
//else 向前边或u、v不在同一连通块 对cc没有贡献
}
if(~fa && c == 1)isCut.set(u,(bool)0);/*特判 单个子树 root可以剥离而不影响图联通*/
return lu;//返回lowU
}
inline int jdg(int u,int v){
if(!(_get(u) == _get(v)))return 0;/*不连通*/
if(bccID[u].size() > bccID[v].size())std::swap(u,v);/*哈希查找 小集合做查找记录*/
for(auto &e:bccID[v])
if(bccID[u].count(e)){//处于同一连通块
if(bcc[e].size() == 2)return 1;//特判 两点一边的特殊BCC
else return 2;
}
return 1;//不同连通块
}
文章讨论了解决无重边无自环图中,给定两点间可达路径数量的问题,利用并查集(tarjan算法)和双连通分量来区分不同情况,包括路径在同一个双连通分量、不同分量以及不连通的情况。
346

被折叠的 条评论
为什么被折叠?



