COCI2021-2022#1 Logičari

P7929 [COCI2021-2022#1] Logičari

题目大意

给你一个有 n n n个点的基环树,现在对这个基环树上的点染色,使得每个点都有且仅有一个与它相连的点(不包括他自己)被染色,求最少的染色点数。如果不存在染色方案,则输出 − 1 -1 −1。

3 ≤ n ≤ 1 0 5 3\leq n\leq 10^5 3≤n≤105

题解

首先,我们知道,基环树是一棵树上加一条边所得到的。下文称这条边为特殊边,我们可以求出特殊边的两个端点 f m fm fm和 t o to to。

设 f i , 0 / 1 / 2 / 3 f_{i,0/1/2/3} fi,0/1/2/3表示 i i i的子树在对应状态下的最小染色点数,对应状态如下:

  • 0 0 0表示点 i i i不染色,且 i i i的不包括父亲的邻接点都没有被染色(如果有点 i i i在特殊边上,特殊边的另一点也为被染色,下同)
  • 1 1 1表示点 i i i不染色,且 i i i的不包括父亲的邻接点中有一个点被染色
  • 2 2 2表示点 i i i被染色,且 i i i的不包括父亲的邻接点都没有被染色
  • 3 3 3表示点 i i i被染色,且 i i i的不包括父亲的邻接点中有一个点被染色

那么,状态为 0 0 0和 2 2 2的点 i i i的父亲必须染色,状态为 1 1 1和 3 3 3的点 i i i的父亲必须不染色。

那么,直接做树型 D P DP DP即可。

对于特殊边上的两个端点 f m fm fm和 t o to to,枚举它们的染色情况(枚举分别是否染色,只要枚举四次),在做树型 D P DP DP时对这两个点特殊处理一下即可。

时间复杂度为 O ( n ) O(n) O(n)。

一些解释

在代码中,在枚举特殊边上的两个端点 f m fm fm和 t o to to的染色情况的时候,用数组 n d nd nd来保存它们的染色情况:

  • n d i = − 1 nd_i=-1 ndi=−1表示这个点不在特殊边上
  • n d i = 0 nd_i=0 ndi=0表示两个点都没被染色
  • n d i = 1 nd_i=1 ndi=1表示自己没被染色,对方被染色了
  • n d i = 2 nd_i=2 ndi=2表示自己被染色了,对方没被染色
  • n d i = 3 nd_i=3 ndi=3表示两个点都被染色了

code

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const long long inf=1e9;
int n,x,y,fm,to,tot=0,d[200005],l[200005],r[200005],dep[100005];
int nd[100005];
long long ans=inf,f[100005][4];
void add(int xx,int yy){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void gt(int u,int fa){
	dep[u]=dep[fa]+1;
	for(int i=r[u];i;i=l[i]){
		if(d[i]==fa) continue;
		if(dep[d[i]]){
			if(dep[d[i]]<dep[u]){
				fm=u;to=d[i];
			}
			continue;
		}
		gt(d[i],u);
	}
}
void dfs(int u,int fa){
	f[u][0]=0;f[u][2]=1;
	long long v1=inf,v2=inf;
	for(int i=r[u];i;i=l[i]){
		if(d[i]==fa) continue;
		if(nd[d[i]]!=-1&&nd[u]!=-1) continue;
		dfs(d[i],u);
		f[u][0]+=f[d[i]][1];
		v1=min(v1,f[d[i]][3]-f[d[i]][1]);
		f[u][2]+=f[d[i]][0];
		v2=min(v2,f[d[i]][2]-f[d[i]][0]);
	}
	f[u][1]=f[u][0]+v1;
	f[u][3]=f[u][2]+v2;
	if(nd[u]!=-1){
		if(nd[u]==0){
			f[u][2]=f[u][3]=inf;
		}
		else if(nd[u]==1){
			f[u][1]=f[u][0];
			f[u][0]=f[u][2]=f[u][3]=inf;
		}
		else if(nd[u]==2){
			f[u][0]=f[u][1]=inf;
		}
		else{
			f[u][3]=f[u][2];
			f[u][0]=f[u][1]=f[u][2]=inf;
		}
	}
}
void dd(int v1,int v2){
	nd[fm]=v1;nd[to]=v2;
	for(int i=1;i<=n;i++){
		f[i][0]=f[i][1]=f[i][2]=f[i][3]=inf;
	}
	dfs(1,0);
	ans=min(ans,min(f[1][1],f[1][3]));
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	gt(1,0);
	memset(nd,-1,sizeof(nd));
	dd(0,0);
	dd(1,2);
	dd(2,1);
	dd(3,3);
	if(ans==inf) ans=-1;
	printf("%d",ans);
	return 0;
}
相关推荐
MessiGo7 分钟前
Bézier 曲线
c++
艾莉丝努力练剑9 分钟前
【C++:智能指针】没有垃圾回收?智能指针来也!破解C++内存泄漏:智能指针原理、循环引用与线程安全详解
大数据·运维·c++·安全·编辑器·智能指针
橘子真甜~1 小时前
C/C++ Linux网络编程9 - TCP服务器实现流程和独立运行
linux·运维·服务器·c++·守护进程·会话组
暗然而日章9 小时前
C++基础:Stanford CS106L学习笔记 4 容器(关联式容器)
c++·笔记·学习
巨人张10 小时前
C++火柴人跑酷
开发语言·c++
Gomiko11 小时前
C/C++基础(四):运算符
c语言·c++
freedom_1024_12 小时前
【c++】使用友元函数重载运算符
开发语言·c++
zmzb010312 小时前
C++课后习题训练记录Day43
开发语言·c++
赖small强12 小时前
【Linux C/C++开发】 GCC -g 调试参数深度解析与最佳实践
linux·c语言·c++·gdb·-g
CAE虚拟与现实13 小时前
C/C++中“静态链接(Static Linking)” 和 “动态链接(Dynamic Linking)释疑
开发语言·c++·dll·动态链接库·lib库