一道旅行者好题 By liuzhangfeiabc - 圆方树 - 学习笔记

在SDOI2018Round2比赛当场学(y)会(y)了怎么写点双以及建圆方树
但是那个题我懒得找当时写的代码了,因此不做记录,那这个题当做学习笔记
题目大意就是给你一张图,每次询问是否存在a到c到b的点不重复路径,abc互不相同。
题解,圆方树就是,新建点双个数个点,这些点称为方点,每个点向点双里面所有点连边,原图中的边不保留,特别的,点双的定义被扩展成删去任意一个点之后图联通(而不是原来的任意两点都有两条点不重复路径),这样一条边也是一个点双。这样新建出来的图是树并且只有方圆边(圆点就是原图中的点)。SDOI的那个题的结论就是虚树点的个数-|S|,而这个题的结论就是,c与 a到b在圆方树上路径 的距离不超过1,即c或fa[c]在a到b的路径上,或者fa[LCA(a,b)]==c。尽管可以线性完成上述判断,但简易的使用树剖判断即可,注意到前两个条件不需要跑两遍树剖,跑一次即可;第二个询问跑完树剖深度较浅的点的fa就是LCA。这样就可以从1100ms+卡到500ms-,效果显著。
代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<stack>
#include<assert.h>
#define N 200000
#define M 600000
#define gc getchar()
using namespace std;
int sta[N],dfn[N],low[N],dfc,ec,nc;
int sz[N],d[N],in[N],fa[N];
struct edges{
	int to,pre;
}e[M];int son[N],h[N],u[M],v[M];
int etop,top[N];stack<int> s;
inline int add_edge(int u,int v) { return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop; }
int tarjan(int x,int fa=0)
{
	sta[x]=1,s.push(x),dfn[x]=low[x]=++dfc;
	for(int i=h[x],y;i;i=e[i].pre)
		if(!sta[y=e[i].to])
		{
			tarjan(y),low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x])
			{
				nc++;
				while(dfn[s.top()]>=dfn[y]) sta[s.top()]=2,ec++,u[ec]=nc,v[ec]=s.top(),s.pop();
				ec++,u[ec]=nc,v[ec]=x;
			}
		}
		else if(sta[y]==1&&y!=fa) low[x]=min(low[x],dfn[y]);
	return 0;
}
int fir_dfs(int x,int f)
{
	fa[x]=f,d[x]=d[f]+1,sz[x]=1;
	for(int i=h[x],y;i;i=e[i].pre)
		if((y=e[i].to)^fa[x])
		{
			sz[x]+=fir_dfs(y,x);
			if(sz[y]>sz[son[x]]) son[x]=y;
		}
	return sz[x];
}
int sec_dfs(int x)
{
	in[x]=++dfc;
	if(son[x]) top[son[x]]=top[x],sec_dfs(son[x]);
	for(int i=h[x],y;i;i=e[i].pre)
		if((y=e[i].to)!=fa[x]&&e[i].to!=son[x]) top[y]=y,sec_dfs(y);
	return 0;
}
inline int query(int x,int y,int c,int f)
{
	while(top[x]^top[y])
	{
		if(d[top[x]]<d[top[y]]) swap(x,y);
		if(in[c]<=in[x]&&in[top[x]]<=in[c]) return 1;
		if(in[f]<=in[x]&&in[top[x]]<=in[f]) return 1;
		x=fa[top[x]];
	}
	if(in[x]>in[y]) swap(x,y);
	return (in[c]>=in[x]&&in[c]<=in[y])||(in[f]>=in[x]&&in[f]<=in[y])||(fa[x]==c);
}
inline int inn()
{
	int x,ch;while((ch=gc)<'0'||ch>'9');
	x=ch^'0';while((ch=gc)>='0'&&ch<='9')
		x=(x<<1)+(x<<3)+(ch^'0');return x;
}
int main()
{
	int n=inn(),m=inn(),q=inn(),a,b,c;
	for(int i=1,u,v;i<=m;i++)
		u=inn(),v=inn(),add_edge(u,v),add_edge(v,u);
	nc=n,ec=0,tarjan(1),memset(h,0,sizeof(h)),etop=0;
	for(int i=1;i<=ec;i++) add_edge(u[i],v[i]),add_edge(v[i],u[i]);
	n=nc,fir_dfs(1,0),top[1]=1,dfc=0,sec_dfs(1);
	while(q--) a=inn(),c=inn(),b=inn(),printf("%s\n",query(a,b,c,fa[c])?"yes":"no");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值