D. Jzzhu and Numbers
题意:给你n(1<=n<=1e6)个数a1,a2,…an(0<=ai<=1e6);问有多少种方案满足 a i 1 & a i 2 & . . . & a i k = 0 ( 1 ≤ k ≤ n ) a_{i1} \& a_{i2} \& ... \& a_{ik} = 0 (1 ≤ k ≤ n) ai1&ai2&...&aik = 0(1 ≤ k ≤ n)
官方题解为:
定义
F
(
x
)
为
x
&
a
i
=
x
的
数
量
F(x) 为 x \& a_{i}=x的数量
F(x)为x&ai=x的数量(很明显,相当于x的父亲的数量)
定义
G
(
x
)
为
x
二
进
制
下
的
1
的
数
量
G(x)为x二进制下的1的数量
G(x)为x二进制下的1的数量
则答案为:
∑
i
=
0
2
20
(
−
1
)
G
(
x
)
∗
2
F
(
x
)
\sum_{i=0}^{2^{20}}(-1)^{G(x)}*2^{F(x)}
∑i=0220(−1)G(x)∗2F(x)
然后没了,这让我等吃瓜群众就懵逼了。。。
学了Sosdp的话(没学先去学一手)很容易的计算出F(x),并且明白他的状态的意义(x的父亲的数量),这里不加以阐述了。
我们要求
a
i
1
&
a
i
2
&
.
.
.
&
a
i
k
=
0
(
1
≤
k
≤
n
)
a_{i1} \& a_{i2} \& ... \& a_{ik} = 0 (1 ≤ k ≤ n)
ai1&ai2&...&aik = 0(1 ≤ k ≤ n),
2
F
(
0
)
2^{F(0)}
2F(0) 代表着所有ai选还是不选的方案,所以我们还需要减去不合理的方案,哪些是不合理的方案呢,很明显了,所有的F[x]单独都是不合理的,全都需要减去.(因为所有的状态都是0的father)
但是这里就有一个问题了,比如 11(二进制) 也是 01(二进制) 的父亲,则我F[1]也是包含F[3]的,并且10也是包含11的,所以呢,这不就是容斥吗。明白之后可以直接写了。
注:因为我感觉那个式子应该为
∑
i
=
0
2
20
(
−
1
)
G
(
x
)
∗
(
2
F
(
x
)
−
1
)
\sum_{i=0}^{2^{20}}(-1)^{G(x)}*(2^{F(x)}-1)
∑i=0220(−1)G(x)∗(2F(x)−1), 然后也AC了,然后我来口胡一下,[0,1<<M]一定有偶数位,+1,-1会抵消,end。
#include <bits/stdc++.h>
const int N=4e6+5;
const int M=21;
const int mod=1e9+7;
#define LL long long
using namespace std;
int n,F[N],a[N],p[N];
inline void add(int &x,int y){
x+=y;
if(x>=mod)x-=mod;
if(x<0)x+=mod;
}
inline int count1(int x){
int cnt=0;
while(x){
if(x&1)
cnt++;
x>>=1;
}
return cnt;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
F[a[i]]++;
}
for(int i=0;i<M;i++)
for(int j=0;j<(1<<M);j++)
if(j&(1<<i))
F[j^(1<<i)]+=F[j];
p[0]=1;
for(int i=0;i<(1<<M);i++)
add(p[i],2*p[i-1]);
int ans=0;
for(int i=0;i<(1<<M);i++){
if(count1(i)&1)
add(ans,-(p[F[i]]-1));
else
add(ans,p[F[i]]-1);
}
cout<<ans;
return 0;
}
另外一题同样有意思的题目:
CounterCode 2015 Subset
注:该题解借鉴于https://blog.csdn.net/weixin_38686780/article/details/100109753,我只用了自己的话加以描述。
给你N(n<=2e5)次操作,有以下三种操作。
- add s
- del s
- cnt s
保证
s
∈
[
0
,
2
16
]
s\in [0,2^{16}]
s∈[0,216]
add s 表示增加一个数 s,
del s 表示删除一个数 s,
cnt s 表示查找满足 a&s=a 的数量。(相当找有多少个儿子)
分析:很容易想到当次操作
2
16
2^{16}
216 的做法,这是肯定不行的。
分析之后发现复杂度跟s的子集有关,相当于 s在二进制下1的数量有关,设cnt[s] 表示 s 在二进制下1的数量,当 cnt[s]<=8时,我们可以直接枚举子集,单次操作
O
(
2
8
)
O(2^{8})
O(28),当 cnt[s]>8 呢?
很容易先想到对 s 取反,设ss为s取反后的数,以前是为了求 a&s=a 的数量 ,现在变为了求 a&ss=0的数量,由于 ss 的 cnt[ss]<=8 ,我们可以枚举子集,然后用容斥算出 cnt[i]<=8的方案数(下面会讲为什么是这样容斥),那 cnt[i]>8的=部分答案我们如何计算呢?因为 cnt[s]>8,a&s=a 等价于算s的儿子数量,我们可以用一个数组F来计算当 cnt[i]>8时,F[i]的儿子数量<==>每个s对齐父亲的贡献为1,可以枚举父亲来实现。
现在讲如何容斥(数学不好,总是在这部分要理解很久。)
前面已经讲了,现在变为了求 a&ss=0的数量,由于 ss 的 cnt[ss]<=8。我们可以用一个数组E来表示: E[i]代表 cnt[s]<=8 并且s为i的父亲的数量,则E[0]肯定是所有的cnt[s]<=8的数量和,我们要减去不合法的,等价于减去 ((i&ss)!=0) 的部分,这个 i 则一定是ss的儿子(或者叫子集),则一路算过去,相当于答案 =
∑
i
=
0
s
s
(
−
1
)
c
n
t
[
i
]
∗
E
[
i
]
(
前
提
是
i
是
s
s
的
儿
子
)
\sum_{i=0}^{ss}(-1)^{cnt[i]}*E[i](前提是i是ss的儿子)
∑i=0ss(−1)cnt[i]∗E[i](前提是i是ss的儿子)
总结一下:
- 当 cnt[s]<=8,直接计算答案,我们用个数组D[i]来判断 i 出现了多少次
- 当 cnt[s]> 8,设ss为s取反,然后容斥计算cnt<=8的的答案,对于cnt>8的答案直接加上F[s]就行。
- 总的时间复杂度 O ( n ∗ 2 8 ) O(n*2^{8}) O(n∗28),我看大佬们还要*个8,我不知道为什么,可以我哪里不会算
#include <bits/stdc++.h>
const int N=4e6+5;
const int M=16;
const int mod=1e9+7;
#define LL long long
using namespace std;
int n,F[N],E[N],D[N];
/**
cnt[s]:s在二进制下1的数量
E[i]:表示cnt[s]<=8的父亲数量,表示:i&s=i
D[i]:表示cnt[s]<=8时i的数量
F[i]:表示cnt[s]> 8时儿子数量,表示:i&s=s
举个例子:
下面的数都是二进制(不足16位前面补0)
E[01111111] 表示值为01111111和11111111的数量
D[01111111] 表示值为01111111的数量
F[1111111111]表示值为1111111111,0111111111,1011111111,...,1111111110的数量
*/
int cnt(int x){
int ans=0;
while(x){
if(x&1)ans++;
x>>=1;
}
return ans;
}
void upd(int s,int val){
int num=cnt(s);
if(num<=8){
D[s]+=val;
for(int i=s;i;i=(i-1)&s)
E[i]+=val;
E[0]+=val; //因为枚举子集并不会算到0
} else {
for(int i=s;i<(1<<16);i=(i+1)|s)
F[i]+=val;
}
}
void query(int s){
int num=cnt(s);
int ans=0;
if(num<=8){
ans+=D[0];
for(int i=s;i;i=(i-1)&s)
ans+=D[i];
} else {
ans+=E[0]+F[s];
int ss=((1<<16)-1)^s;
for(int i=ss;i;i=(i-1)&ss)
if(cnt(i)&1)
ans-=E[i];
else
ans+=E[i];
}
printf("%d\n",ans);
}
int main(){
cin>>n;
while(n--){
char s[10];
int id;
scanf("%s%d",s,&id);
if(s[0]=='a')
upd(id,1);
else if(s[0]=='d')
upd(id,-1);
else
query(id);
}
return 0;
}