给定一个 n n n 个点的基环树,现在对基环树上的点染色,使得每个点都有且仅有一个与他相连的点(不包括它自身)被染色,求最少的染色点数,或者返回无解。
n n n 个点, n n n条边的连通无向图是基环树。
对于基环树上的问题,常把基环树上的环的一条边删去,这样就剩下一棵树,可以做树型 D P DP DP。
要找环上的一条边,可以用并查集。输入时每加一条边就判断连接的两个点 u , v u,v u,v 属于的连通块是否相同,若相同,说明这条边在环上。将 S S S 看作根。
只需枚举 S , T S,T S,T 的染色状态( 4 4 4 种),对每种情况求最小的答案即可。
设 d p u , s , f s , S s , T s dp_{u,s,fs,Ss,Ts} dpu,s,fs,Ss,Ts 表示当前节点 u u u、父亲、 S S S 和 T T T 的染色状态(0/1),所能达到的最少的染色个数(若无解,为 I N F INF INF)
首先先看无解的情况
对于一个点 u u u,下面分类讨论
时间复杂度为 O ( n ) O(n) O(n)
#include
using namespace std;
typedef long long ll;
const ll INF=1e9;
const int N=1e5+1;
int n,vis[N],a[21],b[21],S,T,F[N];
int head[N],nxt[N<<1],to[N<<1],cnt,d[N],fa[N];
ll dp[N][2][2][2][2];
void add(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
int find(int x)
{
return x==F[x]?x:F[x]=find(F[x]);
}
bool pd(int x,int y)
{
return find(x)==find(y);
}
void Add(int x,int y)
{
int a=find(x),b=find(y);
if(a!=b) F[a]=b;
}
void dfs(int u,int f)
{
if(vis[u]){
S=u,T=f;
return;
}
for(int i=head[u];i;i=nxt[i]){
if(to[i]!=f){
vis[i]=1;
dfs(to[i],u);
}
}
}
void Dfs(int u,int f)
{
fa[u]=f;
for(int i=head[u];i;i=nxt[i]) if(to[i]!=f&&(u!=T||to[i]!=S)&&(u!=S||to[i]!=T)) Dfs(to[i],u);
}
ll solve(int u,int s,int fs,int Ss,int Ts)
{
if(dp[u][s][fs][Ss][Ts]!=-1) return dp[u][s][fs][Ss][Ts];
ll ans=INF;
if(u==S&&s!=Ss||u==T&&s!=Ts||u==T&&fs&&Ss) return dp[u][s][fs][Ss][Ts]=INF;
bool pd=fs||u==S&&Ts||u==T&&Ss;
ll ccnt=s;
for(int i=head[u];i;i=nxt[i]){
if(to[i]!=fa[u]){
ccnt+=solve(to[i],0,s,Ss,Ts);
}
}
if(pd) ans=min(ans,ccnt);
else{
for(int i=head[u];i;i=nxt[i]){
if(to[i]!=fa[u]){
ans=min(ans,ccnt-solve(to[i],0,s,Ss,Ts)+solve(to[i],1,s,Ss,Ts));
}
}
}
return dp[u][s][fs][Ss][Ts]=ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) F[i]=i;
for(int i=1,x,y;i<=n;i++){
scanf("%d%d",&x,&y);
if(pd(x,y)) S=x,T=y;
else add(x,y),add(y,x),Add(x,y);
}
memset(dp,-1,sizeof(dp));
Dfs(S,0);
ll ans=INF;
ans=min({ans,solve(S,0,0,0,0),solve(S,0,0,0,1),solve(S,1,0,1,0),solve(S,1,0,1,1)});
if(ans!=INF) printf("%lld\n",ans);
else puts("-1");
}