[BZOJ4671] 异或图(容斥计数+线性基)

题意

  • 定义两个结点数相同的图 G 1 G_1 G1 G 2 G_2 G2 的异或为一个新的图 G G G, 其中如果 ( u , v ) (u, v) (u,v) G 1 G_1 G1 G 2 G_2 G2 中的出现次数之和为 1 1 1, 那么边 ( u , v ) (u, v) (u,v) G G G 中, 否则这条边不在 G G G 中。现在给定 s s s 个结点数均为 n n n 的图 G 1 , . . . , G s G_1, ..., G_s G1,...,Gs ,设 S = G 1 , . . . , G s S = G_1 , ..., G _s S=G1,...,Gs , 求 S S S 有多少个子集的异或为一个连通图。

对于这种连通图计数的问题我们一般考虑反面,即计算不连通图的个数,我们通过枚举集合划分把整个图分成至少有多少个联通块,也就是不同集合间一定不存在边,假设整个图中有 m m m个联通块,那么我们可以写出推容斥系数的式子:

∑ i = 1 m { m i } f i = [ m = 1 ] \sum_{i = 1}^m\begin{Bmatrix} m \\ i \end{Bmatrix}f_i=[m=1] i=1m{mi}fi=[m=1]上面这个式子考虑一下实际意义就是分出来的大集合因为内部是随便连的,所以还有可能被分成很多小集合,所以我们要让大于 1 1 1的联通块算的总次数为 0 0 0,就有了上面这个式子。

打表后可以发现 f i = ( − 1 ) i − 1 ( i − 1 ) ! f_i=(-1)^{i-1}(i-1)! fi=(1)i1(i1)!,或者也可以用斯特林反演来算,那么我们现在就是要算这些图有多少个子集,满足跨过集合的边异或次数为偶数,实际上这个也很好算,我们把一个图看成一个二进制数,把所有的图都插入线性基内,设线性基里面元素个数为 x x x,那么有 2 s − x 2^{s-x} 2sx种方案满足不存在跨过集合的边,这个东西很好理解,加入一个元素不能被线性基的元素异或出来,它在线性基中肯定会占一个位置,然后就没了。


#include <bits/stdc++.h>
 
#define ll long long
#define For(i, a, b) for (int i = a; i <= b; ++ i)
#define Forr(i, a, b) for (int i = a; i >= b; -- i)
 
using namespace std;
 
const int N = 60 + 7;
 
 
int m, s, n, len;
 
ll SS[N], f[N], fac[N]; string S[N];
ll ans;
 
int be[N];
 
vector<int> in[N];
 
void judge() {
    ll tmp[N], fuck = (1ll << len) - 1;
    int cnt = 0;
    For(i, 1, n) For(j, i + 1, n) {
        if (be[i] == be[j]) fuck -= 1ll << cnt;
        ++ cnt;
    }
    ll b[N] = {cnt = 0};
    For(i, 1, s) {
        tmp[i] = SS[i] & fuck;
        Forr(j, len - 1, 0) if (1ll << j & tmp[i]) {
            if (!b[j]) {
                ++ cnt;
                b[j] = tmp[i];
                break;
            }
            tmp[i] ^= b[j];
        }
    }
    ans += pow(2, s - cnt) * f[m];
}
 
void dfs(int x) {
    if (x > n) return judge();
    be[x] = ++ m, in[m].push_back(x);
    dfs(x + 1);
    in[m --].pop_back();
    For(i, 1, m) {
        be[x] = i, in[i].push_back(x);
        dfs(x + 1);
        in[i].pop_back();
    }
}
 
int main() {
#ifdef ylsakioi
    freopen("bzoj4671.in", "r", stdin);
    freopen("bzoj4671.out", "wd", stdout);
#endif
 
    cin >> s; 
    For(i, 1, s) {
        cin >> S[i], reverse(S[i].begin(), S[i].end());
        len = S[i].size();
        For(j, 0, len - 1) SS[i] = SS[i] << 1 | (S[i][j] ^ 48);
    }
    n = (sqrt(8 * len + 1) + 1) / 2;
    f[1] = fac[1] = 1; 
    For(i, 2, n) {
        f[i] = f[i - 1] * -1 * (i - 1);
        fac[i] = 1ll * fac[i - 1] * i;
    }
 
    dfs(1);
    cout << ans << endl;
 
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值