CF 684 div2 D. Graph Subset Problem 题解 图论

一.题目链接

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

二.题意

无向图,n个点m条边.再给一个k. (1<=n,m,k<=1e5)

如果可以找到一个点集(这个点集中的每个点的度数都>=k),输出1,并输出方案;否则,如果可以找到大小为k的完全子图(k个点,两两相连),输出2,并输出方案;否则,输出-1.

三.算法分析

输出1的找法

不断将每个度数<k的点和其所连接的边都去掉,如果最后还剩下点,那就可以输出1了.

输出2的找法

不断将每个度数<k-1的点和其所连接的边都去掉,如果在这个过程中有点u的度数为k-1,并且点u和他的k-1个邻居可以构成完全子图,那就保存答案,可以输出2了.

判定这k个点是否为完全子图: 读入后将g[]排序,然后就可以k × k × log k 的查询了.(因为m<1e5,所以k*k/2<1e5,不会超时)

代码优化

初始化vis=0,队列里vis=1,完全去掉以后vis=2.

可以将两个步骤一起进行,将个度数<k的点都放进队列里去,然后一个一个删除,如果要删除的点u恰好满足输出2的条件,那就保存.

要完全去掉点u时,将vis=1->2,并将其相邻点的度数-1,并且更新队列.

四.代码

#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define sz(x)  (int)x.size()
#define cl(x)  x.clear()
#define all(x)  x.begin() , x.end()
#define rep(i , x , n)  for(int i = x ; i <= n ; i ++)
#define per(i , n , x)  for(int i = n ; i >= x ; i --)
#define mem0(x)  memset(x , 0 , sizeof(x))
#define mem_1(x)  memset(x , -1 , sizeof(x))
#define mem_inf(x)  memset(x , 0x3f , sizeof(x))
#define debug(x)  cerr << #x << " = " << x << '\n'
#define ddebug(x , y)  cerr << #x << " = " << x << "   " << #y << " = " << y << '\n'
#define ios std::ios::sync_with_stdio(false) , cin.tie(0)
using namespace std ;
typedef long long ll ;
typedef long double ld ;
typedef pair<int , int> pii ;
typedef pair<ll , ll> pll ;
typedef double db ;
const int inf = 0x3f3f3f3f ;
const double eps = 1e-6 ;



/*-------------------------------end-----------------------------------------*/
const int maxn=1e5+2000;
int n,m,k,u,v;
vector <int> g[maxn],clique;
queue <int> q;
int d[maxn],vis[maxn];

void init() {
	rep(i,1,n) cl(g[i]),d[i]=0,vis[i]=0;
	while (!q.empty()) q.pop();
	cl(clique);
}

int main() {
//	ios;
	int T;
	cin>>T;
	while (T--) {
		cin>>n>>m>>k;
		init(); // g,d,q,clique,vis
		rep(i,1,m) scanf("%d%d",&u,&v),g[u].pb(v),g[v].pb(u);
		rep(i,1,n) sort(all(g[i])),d[i]=sz(g[i]);
		rep(i,1,n) if (d[i]<k) vis[i]=1,q.push(i);
		while (!q.empty()) 	{
			int u=q.front();
			q.pop(); 
			vis[u]=2;
			if (d[u]==k-1 && clique.empty()) {
				clique.pb(u);
				for (auto v : g[u]) if (vis[v]!=2) clique.pb(v);
				bool flag=1;
				for (auto x : clique) for (auto y : clique) if (x!=y && !binary_search(all(g[x]),y)) flag=0;
				if (!flag) cl(clique);
			}
			for (auto v : g[u]) {
				d[v]--;
				if (!vis[v] && d[v]<k) vis[v]=1,q.push(v);
			}
		}
		int ans=0;
		rep(i,1,n) if (!vis[i]) ans++;
		if (ans) {
			printf("1 %d\n",ans);
			rep(i,1,n) if (!vis[i]) printf("%d ",i);
			printf("\n");
		} else if (!clique.empty()) {
			printf("2\n");
			for (auto x : clique) printf("%d ",x);
			printf("\n");
		} else printf("-1\n");
	}
}

五.总结

  1. aoto很好用,代码量少很多.
  2. 用vis=2表示这个点彻底被删除了,vis=1表示准备处理.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值