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 3n105


题解

首先,我们知道,基环树是一棵树上加一条边所得到的。下文称这条边为特殊边,我们可以求出特殊边的两个端点 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

#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;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值