Codeforces1325 F. Ehab’s Last Theorem(dfs树+抽屉原理)

题意:

给一个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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值