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.8100≈2×10−10,可以忽略不计。也就是说,如果没有抽中有趣点,那么基本上可以放心打-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;
}