传送门
题意:定义两个图的异或的边集为在两张图中恰出现一次的边。给sss张nnn个点的图的集合,求异或和为连通图的子集数。
s≤60,n≤10s \leq 60,n \leq 10s≤60,n≤10
设GiG_iGi表示异或出iii个连通块的子集数,答案就是G1G_1G1
GGG并不好求废话,我们考虑一个看起来很对的求法:
虽然边很多但点很少, 我们可以暴力枚举连通块中的点,复杂度是O(B(n))O(B(n))O(B(n)),BBB为贝尔数,B(10)=21147B(10)=21147B(10)=21147
可以确定的是不同连通块间没有边,但连通块间仍然不好确定,所以不能叫连通块。为了区分,我们把枚举出来的称为「集合」。
设FiF_iFi为异或出iii个集合的子集数,即只要求不同集合间没有边,同一集合间没有要求没有要求。
发现GGG可以表示出FFF:因为FFF的每个「集合」一定是若干连通块构成的非空集合,即一个斯特林数
Fm=∑i=mn{im}GiF_m=\sum_{i=m}^n\left\{\begin{matrix}i\\m\end{matrix}\right\}G_iFm=i=m∑n{im}Gi
斯特林反演一波
Gm=∑i=mn(−1)i−m[im]FiG_m=\sum_{i=m}^n(-1)^{i-m}\left[\begin{matrix}i\\m\end{matrix}\right]F_iGm=i=m∑n(−1)i−m[im]Fi
答案为
G1=∑i=1n(−1)i−1[i1]Fi=∑i=1n(−1)i−1(i−1)!FiG_1=\sum_{i=1}^n(-1)^{i-1}\left[\begin{matrix}i\\1\end{matrix}\right]F_i \\=\sum_{i=1}^n(-1)^{i-1}(i-1)!F_iG1=i=1∑n(−1)i−1[i1]Fi=i=1∑n(−1)i−1(i−1)!Fi
现在我们只需要求出FFF
枚举出集合后,只考虑所有图中跨集合的边(实现时取个&即可),然后需要求出多少个子集异或和为000
把所有图插进线性基,设最终大小为sizsizsiz,那么自由元个数为s−sizs-sizs−siz。首先只含线性基中元素的非空子集不会有贡献,然后对于任意自由元的子集,根据线性基的定义,都可以找到一个线性基的子集把它异或成000。
算GGG的过程中可能会爆ll,但反正最终答案在范围内,溢出就溢出吧。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
using namespace std;
typedef long long ll;
int s,n;
char t[100];
ll h[100],bas[100],f[100],ans;
int col[100];
inline void insert(ll x)
{for (int i=60;i>=0;i--)if (x&(1ll<<i))if (bas[i]) x^=bas[i];else return (void)(bas[i]=x);
}
void dfs(int k,int c)
{if (k>n){ll d=0;for (int i=1,cnt=0;i<n;i++)for (int j=i+1;j<=n;j++,cnt++)if (col[i]!=col[j])d|=1ll<<cnt;memset(bas,0,sizeof(bas));for (int i=1;i<=s;i++) insert(h[i]&d);int cnt=0;for (int i=0;i<=60;i++) cnt+=(bas[i]>0);f[c]+=1ll<<s-cnt;return;}for (col[k]=1;col[k]<=c;col[k]++,dfs(k+1,c));col[k]++,dfs(k+1,c+1);
}
int main()
{scanf("%d",&s);for (int i=1;i<=s;i++){scanf("%s",t);for (int j=strlen(t)-1;j>=0;j--) h[i]=(h[i]<<1)^(t[j]^48);}int len=strlen(t);for (n=1;n*(n-1)/2<len;n++);dfs(1,0);ll fac=1;for (int i=1;i<=n;fac*=i,i++) ans+=((i&1)? 1:-1)*fac*f[i];printf("%lld\n",ans);return 0;
}