题意概括
对于一个给定的无向图,回答 q q q 次询问:在 x , y x,y x,y之间是否有一条经过奇数条边的路径?
分析
定义问题类型:
无向图判断两点之间是否存在经过奇数条边的路径问题
该类问题常用解法
Yes条件:
- u u u 到 v v v 的路径上仅有偶环,但是有奇数条简单路径;
- u u u 到 v v v 的路径上有奇环;
1)判断简单路径:直接处理
d
e
p
t
h
depth
depth
2)判断奇环:求出点双联通分量,在每个点双里面跑黑白染色判断是否有奇环
证明可行性:
点双性质:一个点双连通分量中,要么每条边都在至少一个奇环上, 要么没有奇环。
因此只要有奇环,那么该点双内的所有边都在奇环上。
我们容易发现,若一个点双中存在奇环,那么一定不能对其进行黑白染色。因此我们暴力尝试染色的做法是合理可行的。
具体实现:
我们考虑建立圆方树,将不能染色的方点进行标记,再找出 u u u 到 v v v 的路径上是否有带标记的方点即可。
找标记方点时我们考虑使用树上差分,
实现:
(
s
u
m
[
u
]
+
s
u
m
[
v
]
−
2
∗
s
u
m
[
l
c
a
(
u
,
v
)
]
)
>
0
(sum[u] + sum[v] - 2 * sum[lca(u, v)]) > 0
(sum[u]+sum[v]−2∗sum[lca(u,v)])>0 ,
sum为到根路径上标号的前缀和,倍增求
l
c
a
lca
lca即可。
Code:
//Leaders
#include<bits/stdc++.h>
using namespace std;
const int NUM=2e5+5;
vector<int> g1[NUM],g2[NUM];
vector<int> rtt;
stack<int> sta;
pair<int,int> mp[NUM];
int n,m,q,cnt,root;
int low[NUM],f[NUM][25],rt[NUM],dfn[NUM],vis[NUM],odd[NUM],bj[NUM],flagg[NUM],sum[NUM],co[NUM];
inline void dfs1(int x,int pre,int dep) {
low[x]=dfn[x]=dep;
vis[x]=1;
rt[x]=root;
sta.push(x);
for (auto iter:g1[x]) {
if(iter==pre)
continue;
if(vis[iter]) {
low[x]=min(low[x],dfn[iter]);
} else {
dfs1(iter,x,dep+1);
low[x]=min(low[x],low[iter]);
if(low[iter]>=dfn[x]) {
int temp;
++cnt;
do {
temp=sta.top();
sta.pop();
g2[cnt].push_back(temp);
g2[temp].push_back(cnt);
}
while(temp!=iter);
g2[x].push_back(cnt);
g2[cnt].push_back(x);
}
}
}
}
inline int paint(int x,int color) {
vis[x]=1;
co[x]=color;
for (auto iter:g1[x]) {
if(!bj[iter]) {
continue;
}
if(vis[iter]) {
if(color==co[iter])
return 0;
} else {
if(!paint(iter,color^1))
return 0;
}
}
return 1;
}
inline void dfs2(int x,int dep) {
vis[x]=1;
dfn[x]=dep;
for (auto iter:g1[x]) {
if(!vis[iter]) {
dfs2(iter,dep+1);
}
}
}
inline void dfs3(int x,int pre,int dep) {
dfn[x]=dep+1;
f[x][0]=pre;
for (int i=1;i<=19;++i) {
f[x][i]=f[f[x][i-1]][i-1];
}
sum[x]+=odd[x];
for (auto iter:g2[x]) {
if(iter!=pre) {
sum[iter]+=sum[x];
dfs3(iter,x,dep+1);
}
}
}
inline int getlca(int x,int y) {
if(dfn[x]>dfn[y]) {
swap(x,y);
}
int tempp=dfn[y]-dfn[x];
for (int i=0;i<20;++i) {
if((tempp>>i)&1)
y=f[y][i];
}
if(x==y) {
return x;
}
for (int i=19;i>=0;--i) {
if(f[x][i]!=f[y][i]) {
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
cnt=n;
for (int i=1;i<=m;++i) {
int u,v;
cin>>u>>v;
g1[u].push_back(v);
g1[v].push_back(u);
}
for (int i=1;i<=n;++i) {
if(!vis[i]) {
root=i;
rtt.push_back(i);
while(!sta.empty()) {
sta.pop();
}
dfs1(i,0,1);
}
}
memset(vis,0,sizeof(vis));
for (int i=n+1;i<=cnt;++i) {
for (auto iter:g2[i]) {
bj[iter]=1;
}
odd[i]=(!paint(g2[i][0],1));
for (auto iter:g2[i]) {
vis[iter]=bj[iter]=0;
}
}
for (auto iter:rtt) {
dfs2(iter,1);
}
cin>>q;
for (int i=1;i<=q;++i) {
cin>>mp[i].first>>mp[i].second;
if(rt[mp[i].first]==rt[mp[i].second] && (dfn[mp[i].first]+dfn[mp[i].second])&1) {
flagg[i]=1;
}
}
for (auto iter:rtt) {
dfs3(iter,0,1);
}
for (int i=1;i<=q;++i) {
if(rt[mp[i].first]!=rt[mp[i].second]) {
continue;
}
int lca=getlca(mp[i].first,mp[i].second);
lca=f[lca][0];
if(sum[mp[i].first]+sum[mp[i].second]-2*sum[lca]) {
flagg[i]=1;
}
}
for (int i=1;i<=q;++i) {
if(mp[i].first==mp[i].second) {
flagg[i]=0;
}
if(flagg[i]) {
cout<<"Yes"<<'\n';
} else {
cout<<"No"<<'\n';
}
}
return 0;
}
涉及知识点整合
点双求法:
void tarjan(int x,int fa) {
dfn[x]=low[x]=++tim;
sta.push(x);
for (int i=head[x];i;i=e[i].nxt) {
int y=e[i].to;
if(y==fa) {
continue;
}
if(dfn[y]) {
low[x]=min(low[x],dfn[y]);
} else {
tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]) {
++cnt;
int temp;
do {
temp=sta.top();
sta.pop();
add(cnt,temp);
add(temp,cnt);
}
while(temp!=y);
add(cnt,x);
add(x,cnt);
}
}
}
}
染色过程:
void paint(int x,int color) {
if(flag)
return ;
co[x]=color;
++num[x];
for (i=head[x];i;i=e[i].nxt) {
int y=e[i].to;
if(co[y]==-1)
dfs(y,!x); else if(co[y]==x) {
flag=1;
return ;
}
}
}
建立圆方树方法:
void tarjan(int x,int fa) {
dfn[x]=low[x]=++tim;
sta.push(x);
for (i=head[x];i;i=e[i].nxt) {
int y=e[i].to;
if(y==fa) {
continue;
}
if(dfn[y]) {
low[x]=min(low[x],dfn[y]);
} else {
tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]) {
++cnt;
sta.top()
while(sta.top()!=y) {
add(n+cnt,sta.top());
add(sta.top(),n+cnt);
sta.pop();
}
add(n+cnt,sta.top());
add(sta.top(),n+cnt);
sta.pop();
add(n+cnt,x);
add(x,n+cnt);
}
}
}
}
树上差分做法:
void dfs(int x,int fa,int val) {
depth[x]=depth[fa]+1;
f[x][0]=fa;
init[x]=val;
for (int i=1;(i<<i)<=depth[x];++i) {
f[x][i]=f[f[x][i-1]][i-1];
}
for (int i=head[x];i;i=e[i].nxt) {
int y=e[i].to;
if(y==fa)
continue;
dfs(y,x,val);
}
}