NOIP2023模拟12联测33 滈葕

文章描述了一种将血型配对问题转化为0/1有向图上的2-SAT问题的方法,利用tarjan算法求解,最后给出相应的权值编码。

题目大意

给定一个有nnn个点mmm条边的0/10/10/1权有向图,你要给每个点赋予ABCDABCDABCD中的一个字母使得每条有向边(u,v,w)(u,v,w)(u,v,w)都满足

w=1⇐ ⁣⇒(au,av)∈{(A,D),(A,B),(B,D),(B,A),(C,D),(C,A),(C,B)}w=1\Leftarrow\!\Rightarrow (a_u,a_v)\in\{(A,D),(A,B),(B,D),(B,A),(C,D),(C,A),(C,B)\}w=1(au,av){(A,D),(A,B),(B,D),(B,A),(C,D),(C,A),(C,B)}

如果无解,输出NONONO;否则在第一行输出长度为nnn的由ABCDABCDABCD构成的字符串,第iii个字符表示第iii个点的权值。如果有多解,你只需要任意输出一个。

n≤105,m≤5×105,1≤x,y≤n,z∈{0,1}n\leq 10^5,m\leq 5\times 10^5,1\leq x,y\leq n,z\in\{0,1\}n105,m5×105,1x,yn,z{0,1}(x,y,w)(x,y,w)(x,y,w)中的x,yx,yx,y可能相同。


题解

先放一段与题目有关的材料。

ABOABOABO血型系统是血型系统的一种,把血液分为A,B,AB,OA,B,AB,OA,B,AB,O四种血型。血液由红细胞和血清等组成,红细胞表面有凝集原,血清内有凝集素。根据红细胞表面有无凝集原AAABBB来划分血液类型。红细胞上只有凝集原AAA的为AAA型血,其血清中有抗BBB凝集素;红细胞上只有凝集原BBB的为BBB型血,其血清中有抗AAA凝集素;红细胞上两种凝集原都有的为ABABAB型血,其血清中无凝集素;红细胞上两种凝集原皆无者为OOO型,其血清中两种凝集素皆有。有凝集原AAA的红细胞可被抗凝集素凝集;有凝集原BBB的红细胞可被抗BBB凝集素凝集。配血试验是两个人分别提供红细胞和血清并将其混合,观察是否有凝集反应。

可以发现,ABCDABCDABCD的属性分别对应A,B,AB,OA,B,AB,OA,B,AB,O型血,一条边表示一次配血试验。

ai,bia_i,b_iai,bi分别表示第iii个人的红细胞有无凝集原A,BA,BA,B,则凝集原AAA和抗AAA凝集素的相遇条件为ax∧¬aya_x\land \lnot a_yax¬ay,凝集原BBB和抗BBB凝集素的相遇条件为bx∧¬byb_x\land \lnot b_ybx¬by。因此,每个条件为z=(ax∧¬ay)∨(bx∧¬by)z=(a_x\land \lnot a_y)\lor (b_x\land \lnot b_y)z=(ax¬ay)(bx¬by),那么

  • 如果z=0z=0z=0,则满足¬(ax∧¬ay)∧¬(bx∧¬by)\lnot(a_x\land \lnot a_y)\land \lnot(b_x\land \lnot b_y)¬(ax¬ay)¬(bx¬by),即(¬ax∨ay)∧(¬bx∨by)(\lnot a_x \lor a_y)\land(\lnot b_x\lor b_y)(¬axay)(¬bxby)
  • 如果z=1z=1z=1,则满足(ax∧¬ay)∨(bx∧¬by)(a_x\land \lnot a_y)\lor (b_x\land \lnot b_y)(ax¬ay)(bx¬by),即(ax∨bx)∧(ax∨¬by)∧(bx∨¬ay)∧(¬ay∨¬by)(a_x\lor b_x)\land(a_x\lor \lnot b_y)\land(b_x\lor \lnot a_y)\land(\lnot a_y\lor \lnot b_y)(axbx)(ax¬by)(bx¬ay)(¬ay¬by)

这两种情况都是2−SAT2-SAT2SAT的形式,对于每个iii,在2−SAT2-SAT2SAT的图上令iii表示aia_iaii+ni+ni+n表示¬ai\lnot a_i¬aii+2ni+2ni+2n表示bib_ibii+3ni+3ni+3n表示¬bi\lnot b_i¬bi,然后用tarjantarjantarjan即可。

时间复杂度为O(n+m)O(n+m)O(n+m)

code

#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int n,m,ct=0,tp=0,cl=0,dfn[N*4+5],low[N*4+5],st[N*4+5],co[N*4+5];
vector<int>g[N*4+5];
void add(int xx,int yy){
	g[xx].push_back(yy);
}
void tarjan(int u){
	dfn[u]=low[u]=++ct;st[++tp]=u;
	for(int v:g[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!co[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		++cl;
		while(st[tp]!=u) co[st[tp--]]=cl;
		co[st[tp--]]=cl;
	}
}
int main()
{
//	freopen("dopetobly.in","r",stdin);
//	freopen("dopetobly.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1,x,y,z;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		if(!z){
			add(x,y);add(y+n,x+n);
			add(x+2*n,y+2*n);add(y+3*n,x+3*n);
		}
		else{
			add(x+n,x+2*n);add(x+3*n,x);
			add(x+n,y+3*n);add(y+2*n,x);
			add(x+3*n,y+n);add(y,x+2*n);
			add(y,y+3*n);add(y+2*n,y+n);
		}
	}
	for(int i=1;i<=n*4;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=n;i++){
		if(co[i]==co[i+n]||co[i+2*n]==co[i+3*n]){
			printf("NO");return 0;
		}
	}
	printf("YES\n");
	for(int i=1;i<=n;i++){
		int x=co[i]<co[i+n],y=co[i+2*n]<co[i+3*n];
		if(x&&!y) printf("A");
		else if(!x&&y) printf("B");
		else if(x&&y) printf("C");
		else printf("D");
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值