題目大意:從 N 個數字中選 1 到 k 個,使得這些數字的與運算結果爲 s, 或運算結果爲 t,爲有多少種選擇方案。
首先我們只關注 集合a 中滿足 (x & s) == s && (x & t) == x
的元素 x,稱這個集合爲 X:{x},令 t = t ^ s
,通過預處理,將 t 中爲 1的位提出來,此時問題等價於:s = 0, t = 。
這相當於要從集合 X 中選出 [1,k] 個元素作爲一個新的集合,使得這個新集合中在這 L 位上每一位至少有一個0,至少有一個1,等價於新集合中每一個元素在這 L 位都是不完全相同的。
通過二項式反演來解決這個問題:
設 f(n):恰好 n 位不完全相同的選擇方案;g(n):最多 n 位不完全相同的選擇方案
顯然 是所求解
可以反演得到:
問題在於如何求 :
定義 所選集合中與 U進行與運算結果相同的選擇方案數。
由於與運算結果相同,等價於選出來的集合中:滿足 U 中爲 1 的位,這些數在這一位的值相同;U 中爲 0 的位,這些數在這些位的取值可能相同也可能不同。
假設 U 有 位爲 1,那麼相當於選出來的集合最多有 L - x 位不相同,這恰好是 g(L - x) 的定義
因此
改變枚舉項,先枚舉 U,可以得到
其中 中1的個數
可以通過遍歷一遍數組求得。
代碼:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2 + 10;
int a[maxn], n, k, s, t, tot;
int vis[300010];
ll C[51][51], D[51][51];
int main() {
scanf("%d%d%d%d",&n,&k,&s,&t);
for (int i = 0; i <= n; i++)
D[i][0] = C[i][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++) {
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
D[i][j] = D[i][j - 1] + C[i][j];
}
for (int i = 1, x; i <= n; i++) {
scanf("%d",&x);
if ((x & s) == s && (x & t) == x)
a[++tot] = x;
}
t ^= s;
int cnt = 0;
for (int j = 0; j < 18; j++)
if (t >> j & 1) cnt++;
//printf("%d %d\n",cnt,tot);
for (int i = 1; i <= tot; i++) {
int val = 1, sum = 0;
for (int j = 0; j < 18; j++) {
if ((a[i] >> j & 1) && (t >> j & 1))
sum += val;
if (t >> j & 1)
val *= 2;
}
a[i] = sum;
}
ll ans = 0;
for (int i = 0; i < (1 << cnt); i++)
vis[i] = 0;
for (int t = 0; t < (1 << cnt); t++) {
int s = 1;
for (int j = 0; j < cnt; j++)
if (t >> j & 1) s *= -1;
ll sum = 0;
for (int i = 1; i <= tot; i++) {
sum -= D[vis[a[i] & t]][min(vis[a[i] & t],k)];
vis[a[i] & t]++;
sum += D[vis[a[i] & t]][min(vis[a[i] & t],k)];
}
for (int i = 1; i <= tot; i++)
vis[a[i] & t]--;
ans += s * sum;
}
printf("%lld\n",ans);
return 0;
}