cf1440D Graph Subset Problem 图论 (拓扑序),邻接表二分查找边

题目链接

https://codeforces.com/problemset/problem/1440/D

题意

给出一张图,问是否出现

  1. 大小为K的强联通子图
  2. K个节点具有K个以上邻居的点集
思路

第二种最好求,利用拓扑排序思路,所有度小于k的点入队,挨个删点,更新度数,直到队空,如果没入队的节点数大于等于k,那么存在第二类。

第一种较麻烦,大小为k的子图每个节点度数一定为k-1,子图是原图不断删点生成的,这个过程正好是第二种求取过程。在队列每次取出点时,如果其度数是k-1,就将它所有邻居(已经入队并且取出的除外,这种节点已经被验证过不能形成第一种情况了)加入集合,枚举集合内节点,通过二分查找判断是否联通即可。

注意时间比较紧,可以判断边,当且仅当m*2>(k-1)*k才有可能形成第一种,可以进行优化。

代码

```#include<cstdio>
#include<iostream>
#include<iomanip>
#include<map>
#include<unordered_map>
#include<string>
#include<queue>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib> 
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl "\n"
//#define int long long
//#define double long double
using namespace std;
	typedef long long ll;
	const int maxn=100050;
	const int inf=0x3f3f3f3f;
	int n,m;
    vector<int>e[maxn];
    int du[maxn];
    int inqueue[maxn];
    void init(int n){//memset再次杀我 ^^
        for(int i=0;i<=n;i++) {
            e[i].clear();
            inqueue[i]=0;
            du[i]=0;    
        }
    }
    template <typename T>
	void read(T &x){
    	x=0;
    	char ch=getchar();
    	ll f=1;
	    while(!isdigit(ch)){
	        if(ch=='-')
	            f*=-1;
	        ch=getchar();
	    }
	    while(isdigit(ch)){
	        x=x*10+ch-48;
	        ch=getchar();
	    }
	    x*=f;
	}
	signed main(){
		IOS
        #ifndef ONLINE_JUDGE
		    freopen("D:\\code\\IO\\in.txt","r",stdin);
		    freopen("D:\\code\\IO\\out.txt","w",stdout);
        #endif
        int tn;
        read(tn);
        while(tn--){
            int k;
            read(n),read(m),read(k);
            init(n);
            for(int i=1;i<=m;i++){
                int u,v;
                read(u),read(v);
                e[u].push_back(v),e[v].push_back(u);
                du[u]++,du[v]++;
            }
            for(int i=1;i<=n;i++){
                sort(e[i].begin(),e[i].end());//维护节点有序,便于二分查找
            }
            queue<int>q;
            for(int i=1;i<=n;i++){
                if(du[i]<k)    q.push(i),inqueue[i]=1;//两类答案都需要度至少为k
            }
            vector<int>c,ans;
            while(q.size()){
                int u=q.front();q.pop();
                inqueue[u]=2;//加入了队列,且取出过
                for(auto i:e[u]){//删点更新
                    du[i]--;
                    if(inqueue[i])  continue;
                    if(du[i]<k) q.push(i),inqueue[i]=1;
                }
                if(m*2>=k*(k-1)){//边的限制条件
                    if(du[u]==k-1&&c.empty()){//度为k-1的点可能形成集团
                        c.push_back(u);
                        for(auto i:e[u])   //有三种状态,0,1,2。0的代表度大于k有可能满足条件,2的度小于k,被取出过,那包含他的集团一定不满足条件
                            if(inqueue[i]!=2)c.push_back(i);
                        bool ok=1;
                        for(int i=0;i<c.size();i++){
                            for(int j=i+1;j<c.size();j++){
                                if(!binary_search(e[c[i]].begin(),e[c[i]].end(),c[j])){
                                    ok=0;
                                    break;
                                }
                            }
                            if(!ok) break;
                        }
                        if(!ok) c.clear();
                    }
                }
            }
            for(int i=1;i<=n;i++){
                if(!inqueue[i] )ans.push_back(i);
            }
            if(ans.size()>=k){
                printf("1 %d\n",ans.size());
                for(int i=0;i<ans.size();i++){
                    if(i)   putchar(' ');
                    printf("%d",ans[i]);
                }
                putchar('\n');
            }
            else if(c.size()){
                printf("2\n");
                for(int i=0;i<c.size();i++){
                    if(i)   putchar(' ');
                    printf("%d",c[i]);
                }
                putchar('\n');
            }
            else   
                printf("-1\n");
            
        }
        
		return 0;
	}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值