关于图论练习题Leaders

题意概括

对于一个给定的无向图,回答 q q q 次询问:在 x , y x,y x,y之间是否有一条经过奇数条边的路径?

分析

定义问题类型:

无向图判断两点之间是否存在经过奇数条边的路径问题

该类问题常用解法

Yes条件:

  1. u u u v v v 的路径上仅有偶环,但是有奇数条简单路径;
  2. 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]2sum[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);
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值