很久就开通博客了 无奈人太懒 从来没写过 最近开始学linux 开始慢慢写linux日志了 就顺带着也写点ACM的题解吧
题意:给你n个数a0,a1...a(n-1)(n<=50,ai<=1e15) 和一个数lim(lim<=1e15),让你从n个数中任意选出一些数,使得这些数的xor值小于等于lim,求总的方案数
例如: {1,2} 3 ---> 答案为4 选择方案有 {}(一个都不选) {1} {2} {1,2}
{5,5} 2 ---> 答案为2 方法有 {} , {5,5}
{5,5,5} 2 --- > 答案为 4 方法有 {},{5,5},{5,5},{5,5} // 可把 2 3 4个解分别看为 {a0,a1} {a0,a2} {a1,a2} 视为不同解
思路:先将lim拆分为二进制,假如lim拆分过后为 110010,那么我们可以这样来求
先求a0~a(n-1) 组成 0xxxxx 这种形式的方案数, 其中x表示该位为0或1皆可
再求10xxxx,以此类推,在分别求11000x,110010的方案数
之后把前面求出的值累加即为 最后 答案
求各种情况的方案数需要用到 高斯消元法
具体怎么求呢
假设不存在未知数x ,那么直接以数列a和lim建立系数矩阵 , 然后化简后求出自由元个数,则此方案数即是 (1<<自由元个数)
再来看有未知数x的情况
先假设 n=2 其中 a0 = 010111 , a1 = 111000 , lim = 110010
现在我们求 10xxxx这种情况下的解
我们可以将 令 lim_b = 100000 , b0 = 010000 , b1 = 110000,即使将 a和lim 后面在未知数x中的数字变为0 得到 lim_b和 数列b
再以数列b和lim_b建立系数矩阵,然后求出自由元个数xb,则10xxxx的解个数即为 (1<<xb)
// 这样做可行的原理也很简单,因为未知数x之前的数已经小于lim了,所以不论后面的数字如何组合,也不会让 其XOR值>lim,即后面随意怎么
// 选择搭配都可以,既然如此,就索性把后面包含在未知数x内的元素全部变成0来计算。(本人也是在看了别人的代码后才发现能够这样搞 = =)
下面上代码
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <math.h>
#include <fstream>
#include <algorithm>
#include <stack>
#include <vector>
#include <queue>
using namespace std;
#define REP(i,n) for(int i=0;i<(n);i++)
#define FOR(i,j,k) for(int i=j;i<=(k);i++)
#define ll long long
ll Solve(vector <ll> num,ll lim){
// 构建系数矩阵,我习惯先把每个数横向构造后,再将矩阵转置
int n = num.size(),m = 52;
bool a[55][55];
memset(a,0,sizeof(a));
REP(i,n) REP(j,m)
if(num[i]&(1ll<<j)) a[i][j] = 1;
REP(j,m) if(lim&(1ll<<j)) a[n][j] = 1;
REP(i,55) REP(j,i) swap(a[i][j],a[j][i]); // 转置
swap(n,m); // 转置矩阵后交换 行列数
int top = 0;
REP(j,m) { // 化简矩阵
int tmp = 0;
FOR(i,top,n) if(a[i][j]){
REP(k,m+1) swap(a[top][k],a[i][k]);
tmp = 1;break;
}
FOR(i,top+1,n) if(a[i][j])
REP(k,m+1) a[i][k] ^= a[top][k];
top += tmp;
}
REP(i,n) if(a[i][m]){ // 判断是否无解
bool flag = 0;
REP(j,m) if(a[i][j]) flag = 1;
if(!flag) return 0;
}
return (1ll<<(m-top)); // 自由元个数即为 未知数个数-主元列数(m-top)
}
class XorCards{
public:
long long numberOfWays(vector<long long> number, long long limit){
vector <ll> a = number;
ll b = limit;
ll res = Solve(a,b);
REP(i,50) if(limit&(1ll<<i)){
// 下面两句将 包含在未知数x 内的数变为0
b = ((limit>>(i+1)) << (i+1));
REP(j,a.size()) a[j] = ((number[j]>>i) << i);
res += Solve(a,b);
}
return res;
}
};