CF1361E James and the Chase——DFS树

CF1361E James and the Chase

题意

在这里插入图片描述
原题的有向图不一定有20%的节点是有趣的,但是如果不到20%就直接输出-1就可以了,所以我们不妨就按上面的翻译来做。

题解

图的路径问题,一般用DFS树可以极大地简化它。

直接按定义判断需要记录路径,肯定是不现实的。我们把有趣点的定义翻译一下:以这个点为根,建出任意一棵DFS树,那么由于此图强连通,所以这棵树一定可以包含所有点。如果存在两条到点 v v v 的路径,那么除了延树边走的那条,另一条必定会经过横向边或重孙边。反过来,也可以证明,如果树中存在横向边或重孙边,那么必定有一个点可以通过两条及以上路径到达。

所以有趣点的定义翻译一下就是:以这个点为根建出的DFS树中,只有树边和返祖边。

这样我们就可以 O ( n ) O(n) O(n) 地判断某个点是不是有趣点了。但是这样最多只能确定一个有趣点,如何找到其它的呢?

注意到以有趣点为根的DFS树有优良的性质:非树边一定是返祖边,所以我们没必要再退到一棵普通DFS树的情况去想做法。我们假设已经确定了一格有趣点,建出了它的DFS树,那么考虑一下树上任意一个有趣点 u u u(不是根)满足什么条件:

首先,由于任何地方(的非树边)都只有返祖边,所以任何一个点到它的子树内的所有点都只有一条简单路径。所以我们可以仅考虑点 u u u 与它的子树之外的点的关系。

显然,点 u u u 只可能经过从子树内伸出的返祖边到达外面的点。然后画画图会惊奇地发现,如果存在有两条返祖边伸到了 u u u 的上面,假设指向祖先 a 1 a_1 a1 a 2 a_2 a2(从下到上),那么必定有两条不同的简单路径可以从 u u u a 1 a_1 a1,即便 a 1 a_1 a1 a 2 a_2 a2 重合。

所以可以进一步推出,如果 u u u 要是有趣点,那么有且仅有一条返祖边从子树内连到 u u u 的祖先。

我们假设这条返祖边连向 a a a,那么从 u u u 到其它所有点都必须经过点 a a a,相当于转化为以 u u u 为根的DFS树后,这条边下接其它所有点。
在这里插入图片描述
这就要求点 a a a u u u 的子树外的其它点必须只有一条简单路径。结合点 a a a u u u 的子树内的点也只有一条路径,相当于要求点 a a a 也必须是一个有趣点。

综上,我们可以安全地得到一个令人满意的结论:在以有趣点为根的DFS树上,一个不为根的点是有趣点,当且仅当从它的子树往外只连出一条返祖边,且边指向的结点是有趣点。

因此,我们可以先遍历一遍预处理出每棵子树向外连出的最远(最靠近根)的返祖边和次远的边,第二次遍历的时候就可以很方便地判定有趣点了。

最后的问题是,如何找到一个作为根的有趣点。

这题的 t r i c k \rm trick trick 有点多。这里我们采用随机选点的方法,假设选了100次,如果真的有不小于20%的有趣点,那么一次都没抽中的概率最多为 0. 8 100 ≈ 2 × 1 0 − 10 0.8^{100}\approx 2\times 10^{-10} 0.81002×1010,可以忽略不计。也就是说,如果没有抽中有趣点,那么基本上可以放心打-1,抽中了就用。

总复杂度 O ( 100 n ) O(100n) O(100n),当然,你把随机次数设小点也没有问题。

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=200005;
const ll INF=1e18;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[30],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

struct edge{
	int v,to;edge(){}
	edge(int V,int T){v=V,to=T;}
}e[MAXN];
int EN,G[MAXN];
inline void addedge(int u,int v){
	e[++EN]=edge(v,G[u]),G[u]=EN;
}
int n,m;
bool vis[MAXN],zx[MAXN];
inline bool check(int x){
	vis[x]=1,zx[x]=1;
	for(int i=G[x];i;i=e[i].to){
		int v=e[i].v;
		if(vis[v]){
			if(!zx[v])return 0;
		}else if(!check(v))return 0;
	}zx[x]=0;
	return 1;
}
int dep[MAXN],md0[MAXN],md1[MAXN];
inline void addep(int x,int d){
	if(d<md0[x])md1[x]=md0[x],md0[x]=d;
	else if(d<md1[x])md1[x]=d;
}
inline void pdfs(int x,int d){
	dep[x]=d,md0[x]=md1[x]=d;
	for(int i=G[x];i;i=e[i].to){
		int v=e[i].v;
		if(!dep[v]){
			pdfs(v,d+1);
			addep(x,md0[v]),addep(x,md1[v]);
		}else addep(x,dep[v]);
	}
}
bool ite[MAXN];
int rt[MAXN];
inline void dfs(int x){
	if(md0[x]>=dep[x])ite[x]=1;
	if(md0[x]<dep[x]&&md1[x]>=dep[x]&&ite[rt[md0[x]]])
		ite[x]=1;
	rt[dep[x]]=x;
	for(int i=G[x];i;i=e[i].to){
		int v=e[i].v;
		if(dep[v]>dep[x])dfs(v);
	}
}
vector<int>as;
signed main()
{
	mt19937 Rand(*new(int));
	for(int T=read();T--;){
		n=read(),m=read(),EN=0;
		for(int i=1;i<=n;i++)G[i]=0,ite[i]=0;
		for(int i=1,u,v;i<=m;i++)
			u=read(),v=read(),addedge(u,v);
		int x=Rand()%n+1;
		for(int i=1;i<=n;i++)vis[i]=zx[i]=0;
		for(int Tm=100;Tm&&!check(x);Tm--){
			x=Rand()%n+1;
			for(int i=1;i<=n;i++)vis[i]=zx[i]=0;
		}
		for(int i=1;i<=n;i++)vis[i]=zx[i]=0;
		if(!check(x)){print(-1);continue;}
		for(int i=1;i<=n;i++)dep[i]=md0[i]=md1[i]=0;
		pdfs(x,1),dfs(x),as.clear();
		for(int i=1;i<=n;i++)if(ite[i])as.push_back(i);
		if(as.size()*5<n)print(-1,0);
		else for(auto&x:as)print(x,' ');
		END;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值