题意:
给出(s,sleq 60)张图,每张图都有(n,nleq 10)个点。
现在问有多少个图的子集,满足这些图的边“异或”起来后,这张图为连通图。
思路:
-
直接考虑判断图的连通不好判断,所以考虑枚举连通块来进行容斥。
-
定义(f_i)表示有(i)个连通块的答案,发现连通块这个东西也不好处理,我们只能处理出有多少个连通块,但无法确定每个连通块内部的连通关系。
-
定义(g_i)为至少有(i)个连通块的方案数,那么就有关系式:(displaystyle g_i=sum_{j=i}^n egin{Bmatrix} j \ i end{Bmatrix}f_j)。至于为什么要乘以一个第二类斯特林数,相当于我们将单个的连通块再进行组合,有不同的组合方式。
-
此时(g_i)就相当于有(i)个连通块,内部可连通可不连通的方案数。
-
因为最终所求为(f_1),通过斯特林反演可得:(displaystyle f_1=sum_{i=1}^n(-1)^{i-1}egin{bmatrix} i \ 1 end{bmatrix}g_i)。那么现在只需要算(g_i)即可。
-
因为点数很少,可以直接枚举子集划分,我们只需要保证最后不同的集合之间没有边相连即可。
-
当确定了一种子集划分过后,将连接不同集合的边拿出来,将每张图这类边转为二进制插入线性基中,最后的基为(c)个,那么答案就为(2^{s-c})。
-
将答案累加入(f_i)即可。
这个题大概就这样做完了,但还有一些小细节,其中,斯特林反演的时候和常见形式稍有不同,但可以通过反转公式证明:
最后的答案为(2^{s-c})的原因在于,我们相当于来求若干个数异或起来为(0)的方案数,每个图用(x_i)来表示其最终状态,那么如果有(c)个基,说明就有(s-c)个自由变量,对于任意一组自由变量的取值,我们都能找到一组唯一的对应的解满足方程。
其实这个本质上就是求解一个异或方程组。
代码如下:
/*
* Author: heyuhhh
* Created Time: 2019/12/17 15:36:21
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '
'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '
'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 105;
int s, n;
char ch[N];
int G[N][N][N];
int a[N];
ll p[64], fac[N];
ll ans;
void dfs(int x, int up) {
if(x == n) {
memset(p, 0, sizeof(p));
int c = 0;
for(int k = 1; k <= s; k++) {
ll res = 0;
int tot = 0;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
if(a[i] != a[j]) res |= ((ll)G[k][i][j] << tot);
++tot;
}
}
for(int i = tot; i >= 0; i--) if(res >> i & 1) {
if(!p[i]) {
++c; p[i] = res;
break;
}
res ^= p[i];
}
}
if(up & 1) ans += fac[up - 1] * (1ll << (s - c));
else ans -= fac[up - 1] * (1ll << (s - c));
return;
}
for(int i = 1; i <= up + 1; i++) {
a[x + 1] = i; dfs(x + 1, max(i, up));
}
}
void run(){
fac[0] = 1;
for(int i = 1; i < 12; i++) fac[i] = fac[i - 1] * i;
for(int k = 1; k <= s; k++) {
cin >> (ch + 1);
int len = strlen(ch + 1), cnt = 0;
for(int j = 1; !n; j++) if(j * (j - 1) == 2 * len) n = j;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
G[k][i][j] = ch[++cnt] - '0';
}
}
}
a[1] = 1;
dfs(1, 1);
cout << ans << '
';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> s) run();
return 0;
}