CF1228D Complete Tripartite

题意:

有n个点,m条边
将所有点分为三个点集,使每个点集内的点互不相交且与另两个点集中的每个点都有且仅有一条边相连
如果可行,则输出每个点所属的点集编号(1, 2, 3)
反之输出-1

Example

Input
6 11
1 2
1 3
1 4
1 5
1 6
2 4
2 5
2 6
3 4
3 5
3 6
Output
1 2 2 3 3 3

Input
4 6
1 2
1 3
1 4
2 3
2 4
3 4
Output
-1

Solution

  • 根据性质可得:与1相连的点属于点集2、3
  • 再在点集2、3中任选一个点遍历它的相邻点,若点在点集2、3中,则属于点集3
  • 如此这般可以初分3个点集
  • 接下来就是证明是否合法了:
  • 不连通不合法,点集内有点相连不合法,点集内的点没有与另两个点集同时相连不合法
    - 如此这般,轻松搞定

代码祭天!法力无边

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#define il inline
#define re register
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 10;
int n, m, cnt1, cnt2, cnt3;
vector<int> e[maxn];
int vised[maxn], vis[maxn];
//判断连通 \ 判断集合 
int ans[maxn];//答案 => 所属集合 
int edge[maxn];//记录边数 
//int e1, e2, e3;//集合的边数 
il void init() {
	//e1 = e2 = e3 = 0; 
	cnt1 = cnt2 = cnt3 = 0;
	memset(vised, 0, sizeof(vised));
	memset(vis, 0, sizeof(vis));
}
il void dfs(int x) {
	vised[x] = 1;
	for(re int i = 0; i < e[x].size(); ++i) {
		if(!vised[e[x][i]]) dfs(e[x][i]);
	}
	return ;
}
int main() {
	init();
	scanf("%d%d", &n, &m);
	int u, v;
	for(re int i = 1; i <= m; ++i) {
		scanf("%d%d", &u, &v);
		e[u].push_back(v); e[v].push_back(u);
		edge[u]++; edge[v]++;
	}
	dfs(1);//不连通的图肯定不合法 
	for(re int i = 1; i <= n; ++i) {
		if(!vised[i]) {
			printf("-1\n");
			return 0;
		}
	}
	//ans[1] = 1;
	for(re int i = 0; i < e[1].size(); ++i)
		vis[e[1][i]] = 1;//从1遍历一遍,没标记的都是集合1
	bool flag = 1;
	for(re int i = 1; i <= n; ++i) {
		if(!vis[i]) ans[i] = 1;
		else if(vis[i] && flag) {
			//处于2、3集合中,随机抽取一个点归为集合2 
			 for(re int j = 0; j < e[i].size(); ++j) {
			 	if(vis[e[i][j]]) ans[e[i][j]] = 3;
			 	//被选的点相连且与1相连 => 属于集合3 
			 }
			 flag = 0;
		}
	}
	//验证过程:
	for(re int i = 1; i <= n; ++i) {
		if(ans[i] == 0) ans[i] = 2;
		//剩下的点归为集合2
		if(ans[i] == 1)
			cnt1++;
		if(ans[i] == 2)
			cnt2++;
		if(ans[i] == 3)
			cnt3++;
	}
	//1.判断总边数 
	if(cnt1*cnt2 + cnt2*cnt3 + cnt3*cnt1 != m 
	   || cnt1 == 0 || cnt2 == 0 || cnt3 == 0) {
		printf("-1\n");
		return 0;
	}
	//2.判断集合中的点是否有连边
	int s[5];//每个点连的两个集合边 
	for(re int i = 1; i <= n; ++i) {
		memset(s, 0, sizeof(s));
		for(re int j = 0; j < e[i].size(); ++j)
			s[ans[e[i][j]]]++;//i所连的边所属的集合++
		//统计完毕
		if(s[ans[i]] != 0) {
			printf("-1\n");
			return 0;
		}
		if(ans[i] == 1) {
			if(s[2] != cnt2 || s[3] != cnt3) {
				printf("-1\n");
				return 0;
			}
		}else if(ans[i] == 2) {
			if(s[1] != cnt1 || s[3] != cnt3) {
				printf("-1\n");
				return 0;
			}
		}else {
			if(s[1] != cnt1 || s[2] != cnt2) {
				printf("-1\n");
				return 0;
			}
		}
	}
	//3.判断一个集合的所有边数是否等于另两个几个的点数
	for(re int i = 1; i <= n; ++i)
		printf("%d ", ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值