[COCI2021-2022#1] Logičari

题目描述

给定一个 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 = S u=S u=S,但是 s ≠ S s s\not=Ss s=Ss u u u 的状态与设定不同)
  • u = T u=T u=T,但是 s ≠ T s s\not=Ts s=Ts(同上)
  • u = T u=T u=T,但是 S s = 1 , f s = 1 Ss=1,fs=1 Ss=1,fs=1 T T T 的旁边有两个 2 2 2 个染色的)

对于一个点 u u u,下面分类讨论

  • u u u 的子树外,还有与 u u u 连接的点(可能是 f a u , S , T fa_u,S,T fau,S,T)已经被染色。
    说明 u u u 的儿子不能被染色。
    转移方程为 d p u , s , f s , S s , T s = s + ∑ v ∈ s o n u d p v , 0 , s , S s , T s dp_{u,s,fs,Ss,Ts}=s+\sum\limits_{v\in son_u}dp_{v,0,s,Ss,Ts} dpu,s,fs,Ss,Ts=s+vsonudpv,0,s,Ss,Ts
  • 否则说明 u u u 的儿子可以被染色,但只能有一个。
  • 转移方程为 d p u , s , f s , S s , T s = s + min ⁡ v ∈ s o n u ( ( ∑ x ∈ s o n u d p x , 0 , s , S s , T s ) − d p v , 0 , s , S s , T s + d p v , 1 , s , S s , T s ) dp_{u,s,fs,Ss,Ts}=s+\min\limits_{v\in son_u}\left(\left(\sum\limits_{x\in son_u}dp_{x,0,s,Ss,Ts}\right)-dp_{v,0,s,Ss,Ts}+dp_{v,1,s,Ss,Ts}\right) dpu,s,fs,Ss,Ts=s+vsonumin((xsonudpx,0,s,Ss,Ts)dpv,0,s,Ss,Ts+dpv,1,s,Ss,Ts)

时间复杂度为 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");
}

你可能感兴趣的:(算法,深度优先,图论)