题目大意
对一个有向图
(
1
≤
n
≤
47
)
(1\leq n\leq47)
(1≤n≤47),定义
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;
}