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和 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来保存它们的染色情况:
#include
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;
}