CF1361E James and the Chase

一、题目

点此看题

二、解法

首先考虑如何判断一个点合法,以这个点为根建出 \(\tt dfs\) 树,当且仅当这棵树中只存在树边和返祖边时合法,那么判定单点合法

貌似没有什么好的思路,考虑有解合法点数至少有 \(\frac{n}{5}\) 个,可以利用这个性质找一个合法的点,如果我们随机 \(k\) 次,那么错误的概率是 \((\frac{4}{5})^k\),取 \(k=100\) 就基本上稳对了,如果找不到就判定为无解。

设找到的合法点为 \(rt\),那么以 \(rt\) 建出一棵 \(\tt dfs\) 树,然后我们考虑一个结点 \(u\) 怎么样才能合法。

找必要条件,由于只有返祖边,所以 \(u\) 只能通过返祖边走出这个子树,再考虑子树内走出子树的返祖边只能有一个。因为如果有两个那么到 \(u\) 的父亲就有两种方案,如果没有那么走不出子树。

那么返祖边连向的点 \(v\) 有什么性质呢?很显然他必须要是好的,因为要通过它走到子树外的点,而如果从 \(v\) 开始走到某个点有多条简单路径那么 \(u\) 走到它也有多条简单路径,因为只能走简单路径所以不能通过 \(v\) 走到子树内。

那么我们启发式合并求出子树内的返祖边即可,可以用 \(\tt set\) 之类的数据结构维护,时间复杂度 \(O(n\log^2 n)\)

三、总结

图论问题一定要主动放在 \(\tt dfs\) 树上去考虑。

本题的难点是对于 \(20\%\) 的思考,我的理解是这是一个关键的阈值,如果达到了 \(20\%\) 就能做某事,如果达不到 \(20\%\) 就可能做不成某事(不是一定做不成),那么思考这件事是什么即可。

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <ctime>
#include <set>
using namespace std;
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,rt,ans,Ind,id[M],fa[M],up[M],ok[M],in[M],out[M];
vector<int> g[M];multiset<int> s[M];
void dfs1(int u)
{
	in[u]=++Ind;
	for(auto v:g[u])
		if(!in[v]) fa[v]=u,dfs1(v);
	out[u]=Ind;
}
int check(int x)
{
	//printf("%d\n",x);
	for(int i=1;i<=n;i++)
		in[i]=out[i]=fa[i]=0;
	Ind=0;dfs1(x);
	for(int u=1;u<=n;u++)
		for(auto v:g[u]) if(fa[v]^u)
			if(in[v]>in[u] || out[v]<out[u])
				return 0;
	return 1;
}
void dfs2(int u)
{
	for(auto v:g[u])
	{
		if(in[v]<=in[u])//u's ancestor
			s[u].insert(v);
		else
		{
			dfs2(v);
			if(s[u].size()<s[v].size()) swap(s[u],s[v]);
			for(auto x:s[v]) s[u].insert(x);
		}
	}
	s[u].erase(u);
	if(s[u].size()==1) up[u]=*s[u].begin();
}
void dfs3(int u)
{
	ok[u]|=ok[up[u]];
	if(ok[u]) ans++;
	for(auto v:g[u])
		if(in[v]>in[u]) dfs3(v);
}
void work()
{
	n=read();m=read();rt=ans=0;
	for(int i=1;i<=n;i++)
	{
		g[i].clear();s[i].clear();
		id[i]=i;ok[i]=up[i]=0;
	}
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		g[u].push_back(v);
	}
	random_shuffle(id+1,id+1+n);
	for(int i=1;i<=min(n,100);i++)
		if(check(id[i]))
		{
			rt=id[i];
			break;
		}
	if(!rt) {puts("-1");return ;}
	dfs2(rt);
	ok[rt]=1;
	dfs3(rt);
	if(ans*5<n) {puts("-1");return ;}
	for(int i=1;i<=n;i++)
		if(ok[i]) printf("%d ",i);
	puts("");
}
signed main()
{
	T=read();srand(time(0));
	while(T--) work();
}
posted @ 2021-08-18 19:46  C202044zxy  阅读(169)  评论(0编辑  收藏  举报