Counting Good Teams | Gym - 101484K 有
n
n
n 个人,
m
m
m 个课。每个人有一个擅长科目的集合
S
i
∈
[
0
,
2
m
)
S_i\in[0,2^m)
Si∈[0,2m) 你要选择一个两个人的队伍,使得这两个人每个人都至少有一门科目是他会的但是对方不会的。 问选择的方案数。
范围
2
≤
n
≤
1
0
5
2\le n\le 10^5
2≤n≤105
2
≤
m
≤
21
2\le m\le21
2≤m≤21
思路
正面枚举不好做,我们考虑反面情况,就是这两个人会的领域,其中一个人是另一个人的子集。 设
d
p
[
S
]
dp[S]
dp[S] 表示会的领域为
S
S
S 的情况下,子集的数目。 则根据定义,
d
p
[
S
]
=
∑
T
⊂
S
d
p
[
T
]
dp[S]=\sum_{T\sub S}dp[T]
dp[S]=∑T⊂Sdp[T] 但是我们暴力枚举子集的子集,时间复杂度为
O
(
3
n
)
O(3^n)
O(3n) (为什么,可以作为课后习题) 但是我们有更优的策略来达到
O
(
n
×
2
n
)
O(n\times2^n)
O(n×2n)
前缀和 三维前缀和,我们可能会这么写
for i =1 to n
for j =1 to n
for k =1 to n
dp[i][j][k]+= dp[i-1][j][k]for i =1 to n
for j =1 to n
for k =1 to n
dp[i][j][k]+= dp[i][j-1][k]for i =1 to n
for j =1 to n
for k =1 to n
dp[i][j][k]+= dp[i][j][k-1]
高维前缀和,求子集
d
p
dp
dp 则可以变成这样:
for i =1 to n
for j =0 to (1<<n)-1if(j &(1<<i))
dp[j]+= dp[j ^(1<<i)]
高维前缀和,求超集
d
p
dp
dp 则可以变成这样:
for i =1 to n
for j =0 to (1<<n)-1if(!(j &(1<<i)))
dp[j]+= dp[j |(1<<i)]
那么,我们对于某一个状态
S
S
S,直接就能得到有多少个状态
T
T
T,满足
T
⊂
S
T\sub S
T⊂S 考虑答案,就是
n
∗
(
n
−
1
)
/
2
n * (n - 1) / 2
n∗(n−1)/2 再减掉非法的方案。有哪些非法的方案? (1)集合
S
S
S 的个数有
c
n
t
[
S
]
cnt[S]
cnt[S] 个,集合
S
S
S 的子集个数有
d
p
[
S
]
dp[S]
dp[S] 个 那么集合
S
S
S 的真子集的个数有
d
p
[
S
]
−
c
n
t
[
S
]
dp[S]-cnt[S]
dp[S]−cnt[S] 个。 那么减掉的数量就是
c
n
t
[
S
]
×
(
d
p
[
S
]
−
c
n
t
[
S
]
)
cnt[S]\times(dp[S]-cnt[S])
cnt[S]×(dp[S]−cnt[S]) (2)集合
S
S
S 和集合
S
S
S 的人的配对也是非法的。 那么减掉的数量就是
c
n
t
[
S
]
×
(
c
n
t
[
S
]
−
1
)
/
2
cnt[S]\times(cnt[S]-1)/2
cnt[S]×(cnt[S]−1)/2
代码
时间复杂度:
O
(
m
×
2
m
)
O(m\times 2^m)
O(m×2m)
ll cnt[MAX];
ll dp[MAX];intmain(){int n,m;scanf("%d%d",&n,&m);for(int i =1;i <= n;++i){int t;scanf("%d",&t);
cnt[t]++;
dp[t]++;}for(int i =0;i < m;++i)for(int j =0;j <(1<<m);++j)if(j &(1<<i))
dp[j]+= dp[j^(1<<i)];
ll ans =(ll)n *(n -1)/2;for(int i =0;i <(1<<m);++i){
ans -= cnt[i]*(dp[i]- cnt[i]);
ans -= cnt[i]*(cnt[i]-1)/2;}printf("%lld",ans);return0;}