一、题目
二、解法
首先考虑如何判断一个点合法,以这个点为根建出 ( t dfs) 树,当且仅当这棵树中只存在树边和返祖边时合法,那么判定单点合法
貌似没有什么好的思路,考虑有解合法点数至少有 (frac{n}{5}) 个,可以利用这个性质找一个合法的点,如果我们随机 (k) 次,那么错误的概率是 ((frac{4}{5})^k),取 (k=100) 就基本上稳对了,如果找不到就判定为无解。
设找到的合法点为 (rt),那么以 (rt) 建出一棵 ( t dfs) 树,然后我们考虑一个结点 (u) 怎么样才能合法。
找必要条件,由于只有返祖边,所以 (u) 只能通过返祖边走出这个子树,再考虑子树内走出子树的返祖边只能有一个。因为如果有两个那么到 (u) 的父亲就有两种方案,如果没有那么走不出子树。
那么返祖边连向的点 (v) 有什么性质呢?很显然他必须要是好的,因为要通过它走到子树外的点,而如果从 (v) 开始走到某个点有多条简单路径那么 (u) 走到它也有多条简单路径,因为只能走简单路径所以不能通过 (v) 走到子树内。
那么我们启发式合并求出子树内的返祖边即可,可以用 ( t set) 之类的数据结构维护,时间复杂度 (O(nlog^2 n))
三、总结
图论问题一定要主动放在 ( t 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
",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();
}