题意
Topcoder 12004
把一堆数分成两堆,要求这两堆各自的按位与和相等,求分的方案数。
\((n \le 50, 0 \le A_i < 2^ {20})\)
题解
注意到这个 \(A_i\) 很小, \(n\) 也很小,看起来适合状压、容斥。
考虑容斥各二进制位,如果一个位置被钦定不合法,那么这位上所有的\(0\)都属于同一个联通块(一个位置合法,当且仅当所有数该位都是1或者两堆各自至少有一个0)
并查集维护,最后统计。
不开long long见祖宗!
代码
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
typedef long long ll;
const int N = 55, lim = 21;
class SetAndSet{
public:
int n, fa[lim][N], cnt[lim], id[lim][N]; ll ans;
int find(int pos, int x){
return fa[pos][x] = ((x == fa[pos][x]) ? x : find(pos, fa[pos][x]));
}
bool merge(int pos, int x, int y){
int fx = find(pos, x), fy = find(pos, y);
if(fx == fy) return 0;
fa[pos][fx] = fy; return 1;
}
void dfs(int pos, int num, int op){
if(pos == lim)
return ans += op * ((1ll << num) - 2), void();
if(pos == 0){
for(int i = 0; i < n; i++) fa[pos][i] = i;
} else {
for(int i = 0; i < n; i++) fa[pos][i] = fa[pos - 1][i];
}
dfs(pos + 1, num, op);
if(cnt[pos]){
for(int i = 2; i <= cnt[pos]; i++)
if(merge(pos, id[pos][i], id[pos][1])) num--;
dfs(pos + 1, num, -op);
}
}
ll countandset(vector<int> A){
n = A.size(); ans = 0;
for(int i = 0; i < lim; i++){
cnt[i] = 0;
for(int j = 0; j < n; j++)
if(!((A[j] >> i) & 1))
id[i][++cnt[i]] = j;
}
dfs(0, n, 1);
return ans;
}
};
/*
O(2^20*nlogn)
十年OI一场空,没开longlong见祖宗
*/