题意:
给一个n个点m条边的无向图,
要求输出一个大小至少为⌈√n⌉个节点的环。
或者输出一个大小正好为⌈√n⌉个节点的独立集
解法:
dfs树的两颗不同子树之间一定没有边,因为如果有边的话,dfs的过程中一定会先走那条边,那么他们会在同一颗子树上。
因此非树边连接的两个点一定在同一个子树上,假设u到v之间有非树边,那么环的大小为d(u)-d(v)+1。
dfs树图例:
设⌈√n⌉=d,如果找到了一个大小至少为d的环(深度差大于等于d-1的非树边),那么直接输出答案。
如果没找到,那么就不存在深度差为d-1的非树边。
那么将顶点按 深度%(d-1)分类。
因为不存在满足条件的环,那么同余的节点之间不会存在直接相连的边,即每个同余的类分别是一个独立集。
因为%(d-1)之后一共有0到d-2这d-1同余类,根据抽屉原理,至少会有一个类,其中的节点数大于等于d,从中挑出d个点即可。
解释抽屉原理在这题的应用:
如果所有类中的点个数都小于d,那么总节点最多为(d-1)*(d-1),小于n,与图中有n个节点相违背。
因此至少有一个类中存在大于等于d个节点。
ps:
其他:由于dfs树不唯一,因此不能用于找最大/最小环。
输出环上的节点也可以用栈,操作方法是进dfs函数的时候入栈,出dfs函数的时候出栈,这样栈内总是一条链。
找到环的时候需要输出栈内的后面一段链。
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
vector<int>g[maxm];
int pre[maxm],dep[maxm];
int mark[maxm];
int cnt[maxm];
int n,m;
int d;
void dfs(int x){
mark[x]=1;
for(int v:g[x]){
if(!mark[v]){
dep[v]=dep[x]+1;
pre[v]=x;
dfs(v);
}else{
if(dep[x]>dep[v]&&dep[x]-dep[v]+1>=d){//因为是无向边,所以d[v]-d[x]+1>=d也行
vector<int>ans;
int now=x;
while(now!=v){
ans.push_back(now);
now=pre[now];
}
ans.push_back(v);
cout<<2<<endl;
cout<<ans.size()<<endl;
for(int t:ans){
cout<<t<<' ';
}
exit(0);
}
}
}
}
signed main(){
cin>>n>>m;
d=sqrt(n);
while(d*d<n)d++;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1);
for(int i=1;i<=n;i++){
cnt[dep[i]%(d-1)]++;
}
int pos=0;
for(int i=0;i<=d-2;i++){//找点最多的同余类
if(cnt[i]>cnt[pos]){
pos=i;
}
}
cout<<1<<endl;
int need=d;
for(int i=1;need;i++){
if(dep[i]%(d-1)==pos){
cout<<i<<' ';
need--;
}
}
return 0;
}