牛客多校一 H.XOR(线性基)

原题地址:https://ac.nowcoder.com/acm/contest/881/H

核心知识点:
1.对于任何在线性基外的元素,在基内有且只有一种方法使得两者的异或和为0
2.一组数可能会有多种线性基的方案,但是不变的是线性基的数量。

题意:给出一个集合,让求所有子集异或和为0时,子集的大小之和,

思路:首先直接计算子集的大小之和不是很好下手,可以将问题转化为求每个数在子集中出现的次数,然后算n个数产生的总贡献即可。

然后对于这种异或和问题,首选用异或和处理问题。
我们对一开始的n个数字求一次线性基,假如得到一个大小为r的基,那么对于基外的n-r个元素,我们假设选取数字x计算其贡献,选了x之后还剩余n-r-1个元素在基外,那么对于数字x就有 2 n − r − 1 2^{n-r-1} 2nr1种方案选择x的子集。

说明一下为什么只用基外的元素来挑选方案。
因为假设你在选中了x之外还挑选了基内的某些元素试图找到更多的方案数,如果你选中的元素中本来就不是使得x的异或和为0的话,那么你加上这个元素就不可能再使得异或为0了,因为由线性基得性质可得,经过消元后得线性基,第i位为1的最多只会有一个数,如果你选择了这个数,就不可能再消掉这位上的1了。
如果你选中的本来就是使x异或为0的元素,那么你会发现最后挑出来的使同一种方案。
所以选择方案只需要用基外的元素即可。

现在挑完了n-r个基外元素,考虑基中的r个元素的贡献。
我们可以枚举基中的元素,对除了选中的数之外的n-1个数求一次线性基,判断选中的元素还能否插入到新求得线性基中,如果能,说明选中了当前元素就不可能会有异或和为0的方案。如果能就会产生 2 n − r − 1 2^{n-r-1} 2nr1种方案,理由同上。至于基的数量也是r,参考知识点2.
最后,关于求n-1个元素的线性基,不用每次真的再去求一遍,求出n-r个元素的基,每次重新插入r-1个元素即可。

#include 
#define eps 1e-8
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)+1
#define CLR(x,y) memset((x),y,sizeof(x))
#define fuck(x) cerr << #x << "=" << x << endl
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int seed = 131;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int MAXL = 63;

bool Insert(ll a[], long long t) {
    for (int j = MAXL; j >= 0; j--) {
        if (!t) return false;
        if (!(t & (1ll << j))) continue;
        if (a[j]) t ^= a[j];
        else {
            for (int k = 0; k < j; k++) if (t & (1ll << k)) t ^= a[k];
            for (int k = j + 1; k <= MAXL; k++) if (a[k] & (1ll << j)) a[k] ^= t;
            a[j] = t;
            return true;
        }
    }
    return false;
}



int n;
ll A[maxn], B[maxn]; //a是读入数组,b是去掉基之后的数组
ll a[65], b[65]; //对应的线性基
vector<ll>res;//存放基中元素
//bool vis[maxn];
ll fac[maxn];
ll c[65];//拷贝B数组后合并r-1个元素
int main() {
    fac[0] = 1;
    for (int i = 1; i <= 100000; i++) fac[i] = fac[i - 1] * 2 % mod;
    while (~scanf("%d", &n)) {
        res.clear();
        CLR(a, 0);
        CLR(b, 0);
        CLR(c, 0);
        for (int i = 1; i <= n; i++) scanf("%lld", &A[i]);
        int r = 0;//记录基内数量
        int cnt = 0;//记录基外数量
        for (int i = 1; i <= n; i++) {
            if (Insert(a, A[i])) {
                r++;
                res.push_back(A[i]);
            } else {
                B[++cnt] = A[i];
            }
        }
        if (n == r) {
            printf("0\n");
            continue;
        }
        for (int i = 1; i <= cnt; i++) {
            Insert(b, B[i]);
        }
        ll ans = (n - r) * fac[n - 1 - r] % mod;
        int sz = res.size();
        for (int i = 0; i < sz; i++) {//枚举r中的元素
            for (int i = 0; i < MAXL; i++) {
                //拷贝一遍B数组
                c[i] = b[i];
            }
            for (int j = 0; j < sz; j++) {
                if (i == j) continue;
                Insert(c, res[j]);
            }

            if (Insert(c, res[i])) continue;
            else ans = (ans + fac[n - 1 - r]) % mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}



你可能感兴趣的:(ACM_线性基)