【CodeForces908H】New Year and Boolean Bridges (FWT)

题目大意

对一个有向图 ( 1 ≤ n ≤ 47 ) (1\leq n\leq47) (1n47),定义 f ( u , v ) f(u,v) f(u,v)的值为true,当且仅当存在一条路径使得 u u u能走到 v v v
给一个“邻接矩阵” A [ i ] [ j ] A[i][j] A[i][j]
如果A[i][j]=='A',则f(u,v) and f(v,u)true
如果A[i][j]=='X',则f(u,v) xor f(v,u)true
如果A[i][j]=='O',则f(u,v) or f(v,u)true
(保证矩阵对称)

求构造一个有向图,满足邻接矩阵A,最少需要多少条边。

题解

首先可以知道整个图一定联通(以上不论哪个条件都没有说两个点完全不连通的情况)
然后,如果A[i][j]=='A',则 i i i j j j一定在一个强连通块里面,先用并查集处理所有强连通块。
一个点数为size(size>=2)的强连通块,需要size条边连出这个强连通块(一个环)。设总共有m个强联通块,则又需要m-1条边将连通块连成一棵树。

并查集处理完后,一些强连通块可以合并。
显然,连通块越少越好(合并一次,少一条树边),但是,存在xor关系的强连通块不能合并。
siz>=2的强连通块最多23个,用状压表示, g k [ S ] g_k[S] gk[S]表示连k条树边,集合为S的连通块能否连通。
先预处理出 f [ S ] = g 0 [ S ] f[S]=g_0[S] f[S]=g0[S],即将集合中存在xor关系的两个连通块标记为0,其余的都为1
设T为S的子集,转移
g k + 1 [ S ]   ∣ ∣ = g k [ T ]   & &   g k [ S   x o r   T ] g_{k+1}[S]\ ||=g_k[T]\ \&\&\ g_k[S\ xor\ T] gk+1[S] =gk[T] && gk[S xor T]
实际上这样转移也可以(S与T不作限制)
g k + 1 [ S   o r   T ]   + = g k [ S ] × g k [ T ] g_{k+1}[S\ or\ T]\ +=g_k[S]\times g_k[T] gk+1[S or T] +=gk[S]×gk[T]
正好是或运算卷积,可以利用FWT做
于是我们可以从 g 0 g_0 g0开始,不停的卷上 f f f,直到 g k [ a l l ] g_k[all] gk[all]不为0,则需要k条树边。
为了简化,每次卷上 f f f后,不用再逆FWT转换回来,可以事先计算每一个位置对all的贡献,只计算all的系数即可

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=50,MAXS=(1<<24)+10;

int n;
char adj[MAXN][MAXN];

int fa[MAXN],siz[MAXN];
int Root(int u)
{
    if(fa[u]==0)
        return u;
    return fa[u]=Root(fa[u]);
}
void Union(int u,int v)
{
    int r1=Root(u),r2=Root(v);
    if(r1==r2)
        return;
    fa[r1]=r2;
    siz[r2]+=siz[r1];
}

void FWT(int A[],int n)
{
    for(int i=1;i<n;i<<=1)
        for(int j=0;j<n;j+=(i<<1))
            for(int l=j,r=j+i;l<i+j;l++,r++)
                A[r]+=A[l];
}

int m,id[MAXN];
int f[MAXS],g[MAXS],a[MAXS];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%s",adj[i]+1);

    for(int i=1;i<=n;i++)
        siz[i]=1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
            if(adj[i][j]=='A')
                Union(i,j);

    int ans=0;

    for(int i=1;i<=n;i++)
    {
        int r=Root(i);
        if(r==i)
        {
            if(siz[r]>=2)
                id[r]=++m;
            ans+=siz[r];
        }
    }

    if(m==0)
    {
        printf("%d\n",n-1);
        return 0;
    }

    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(adj[i][j]=='X')
            {
                int r1=Root(i),r2=Root(j);
                if(r1==r2)
                {
                    puts("-1");
                    return 0;
                }
                if(id[r1]&&id[r2])
                    f[(1<<(id[r1]-1))|(1<<(id[r2]-1))]=1;
            }
    for(int s=1;s<(1<<m);s++)
        for(int i=1;i<=n;i++)
            if(s&(1<<(i-1)))
                f[s]|=f[s^(1<<(i-1))];
    for(int s=0;s<(1<<m);s++)
        f[s]=f[s]^1;

    FWT(f,1<<m);

    for(int i=0;i<(1<<m);i++)
        a[i]=1;
    for(int i=1;i<(1<<m);i<<=1)
        for(int j=0;j<(1<<m);j+=(i<<1))
            for(int l=j;l<i+j;l++)
                a[l]*=-1;

    for(int i=0;i<(1<<m);i++)
        g[i]=1;
    for(int i=1;;i++)
    {
        int tmp=0;
        for(int j=0;j<(1<<m);j++)
            g[j]*=f[j];
        for(int j=0;j<(1<<m);j++)
            tmp+=g[j]*a[j];
        if(tmp)
        {
            printf("%d\n",ans+i-1);
            return 0;
        }
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值