[CF173D]Deputies

280 篇文章 1 订阅
122 篇文章 0 订阅

题目

传送门 to luogu

思路

由于这是一个二分图,先对其染色为黑、白。染色后,黑色点内部无边,白色点内部无边,故三个白色点是可以任意选取的。黑色点同理。

除去此二情况,只可能是 “一黑两白” 或者 “一白两黑” 。若二者均存在,则将其替换为 “三白” 与 “三黑” ,于是我们认为只有其中一种。若其中一种出现三次,亦可以将其替换为若干个 “三白” 与若干个 “三黑” 。

现在考虑整张图内白色点的个数,若其模 3 3 3 1 1 1 ,则一定是一个 “一白两黑” 或两个 “一黑两白” ,剩下的组成 “三黑” 与 “三白” 。(注意我前面所说,二者只能有一种,且不超过三个。)

对于 “一白两黑” ,很好处理,只需要看是否存在一个白点,其度数不超过黑点的数量减 2 2 2 。因为这样便一定有至少两个黑点与该白点无边,将其配对。

对于两个 “一黑两白” ,同样好处理,枚举一个黑点,其度数不超过白点的数量减 2 2 2 ,道理同上。两个黑点不会共用白点,因为边是双向的,若如此,则 “一白两黑” 已然存在。

找到了关键点就可以暴力找到与他匹配的点了。复杂度?我忘了,好像是 O ( n ) \mathcal O(n) O(n)

至于联通块数量的讨论,还有些可以说的。超过三个联通块时,先乱连,归约为三个。

我们可以想办法让黑白均为三的倍数——无非就是要选出 “一黑两白” 或 “一白两黑” 。

讨论大小为 1 1 1 的联通块——也就是单独的点——的个数(因为其他联通块必然黑白均有,不妨将其抽象为一白一黑的):

  • 若为 1 1 1 ,则总能找到 “一黑两白” 与 “一白两黑” 。
  • 若为 2 2 2 ,细致分类一下。
    • 若这两个单独的点同色,不妨设其颜色为黑,则 “一白两黑” 已然存在。如果我们想凑出 “一黑两白” ,显然白点均与第三个联通块中,与这两个点中任意一个搭配即可。
    • 若这两个点不同色,那么 “一黑两白” 和 “一白两黑” 都存在了。
  • 若为 3 3 3 ,显然可以……三个单独的点可还行。

我们都能做到。结束。

两个联通块时,有两种本质不同的染色方案,都试一试就行了。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
template < typename T >
void getMax(T&a,const T&b){if(a<b)a=b;}
template < typename T >
void getMin(T&a,const T&b){if(b<a)a=b;}

const int MaxN = 100015;
namespace ufs{
	int fa[MaxN];
	void init(int n){
		for(int i=1; i<=n; ++i)
			fa[i] = i;
	}
	int find(int a){
		if(fa[a] != a)
			fa[a] = find(fa[a]);
		return fa[a];
	}
	bool linked(int a,int b){
		return find(a) == find(b);
	}
	void link(int a,int b){
		fa[find(a)] = find(b);
	}
}

struct Edge{
	int to, nxt;
	Edge(int T=0,int N=0){
		to = T, nxt = N;
	}
} edge[MaxN<<1];
int head[MaxN], cntEdge, deg[MaxN];
void addEdge(int a,int b){
	edge[cntEdge] = Edge(b,head[a]);
	head[a] = cntEdge ++;
	edge[cntEdge] = Edge(a,head[b]);
	head[b] = cntEdge ++;
	++ deg[a], ++ deg[b];
}

int color[MaxN], tmp[MaxN];
void colour(int x){
	++ tmp[color[x]];
	for(int i=head[x]; ~i; i=edge[i].nxt)
		if(color[edge[i].to] == 0){
			color[edge[i].to] = 3-color[x];
			colour(edge[i].to);
		}
}

int n, m, ans[MaxN];
void match(){
	int ppl = 1, xez = 0;
	for(int i=1; i<=n; ++i)
		if(color[i] == 1 && !ans[i]){
			ans[i] = ppl, ++ xez;
			if(!(xez%=3)) ++ ppl;
		}
	for(int i=1; i<=n; ++i)
		if(color[i] == 2 && !ans[i]){
			ans[i] = ppl, ++ xez;
			if(!(xez%=3)) ++ ppl;
		}
	puts("YES");
	for(int i=1; i<=n; ++i)
		printf("%d ",ans[i]);
	exit(0);
}
bool xyx[MaxN];
void check(){
	if(tmp[1]%3 == 0) match();
	if(tmp[2]%3 == (tmp[1]+1)%3){
		for(int i=1; i<=n; ++i)
			color[i] = 3-color[i];
		swap(tmp[1],tmp[2]);
	}
	if(tmp[1]%3 == (tmp[2]+1)%3){
		for(int i=1; i<=n; ++i)
			if(color[i] == 2)
			if(deg[i] < tmp[1]-1){
				ans[i] = n/3;
				int sysy = 2;
				for(int j=head[i]; ~j; j=edge[j].nxt)
					xyx[edge[j].to] = 1;
				for(int j=1; j<=n; ++j)
					if(!xyx[j] && color[j] == 1){
						ans[j] = n/3;
						if(!(--sysy)) break;
					}
				match();
			}
		int sxy = 0;
		for(int i=1; i<=n; ++i)
			if(color[i] == 1)
			if(deg[i] < tmp[2]-1)
				++ sxy; // 2* 1+2+2
		if(sxy >= 2){
			int sysy = 2;
			for(int i=1; i<=n; ++i)
				if(color[i] == 1)
				if(deg[i] < tmp[2]-1){
					for(int j=1; j<=n; ++j)
						xyx[j] = false;
					for(int j=head[i]; ~j; j=edge[j].nxt)
						xyx[edge[j].to] = true;
					int lqy = 2;
					ans[i] = n/3+sysy-2;
					for(int j=1; j<=n; ++j)
						if(!xyx[j] && color[j] == 2){
							ans[j] = n/3+sysy-2;
							if(!(--lqy)) break;
						}
					if(!(--sysy)) break;
				}
			match();
		}
	}
}

int main(){
	n = readint(), m = readint();
	ufs::init(n); int tot = n;
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	for(int a,b; m; --m){
		a = readint(), b = readint();
		addEdge(a,b);
		if(!ufs::linked(a,b))
			-- tot, ufs::link(a,b);
	}
	int ok = 0;
	if(tot >= 3){
		for(int i=1; i<=n; ++i)
			if(color[i] == 0){
				color[i] = 1;
				colour(i);
			}
		tot = 1;
	}
	if(tot == 2){
		color[1] = 1, colour(1);
		int root; // 另一个联通块
		for(int i=1; i<=n; ++i)
			if(color[i] == 0){
				root = i; break;
			}
		for(int i=1; i<=n; ++i)
			tmp[i+2] = color[i];
		tmp[n+3] = tmp[1];
		tmp[n+4] = tmp[2];
		for(int d=1; d<3; ++d){ // 做两次
			tmp[1] = tmp[n+3];
			tmp[2] = tmp[n+4];
			for(int i=1; i<=n; ++i)
				color[i] = tmp[i+2];
			color[root] = d, colour(root);
			check();
		} // 做两次
	}
	if(tot == 1){
		if(color[1] == 0)
			color[1] = 1, colour(1);
		check();
	}
	puts("NO");
	return 0;
}

后记

Z X Y \tt ZXY ZXY 大佬说,两个联通块可能不需要翻转。我太弱了,我也不知道是不是这样的。

然后 X E Z \tt XEZ XEZ 大佬不翻转颜色过掉了。啊这……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值