子集 dp
sosdp,高维前缀和。
传统的,计算二维前缀和的递推式为:
s i , j ← s i − 1 , j + s i , j − 1 − s i − 1 , j − 1 s_{i,j}\leftarrow s_{i-1,j}+s_{i,j-1}-s_{i-1,j-1} si,j←si−1,j+si,j−1−si−1,j−1
这可以等价于,每一维上,这个点加上该维上一个点的和。我们不妨把它拓展到 n n n 维,用二进制位表示高维。
1.高维前缀和
给定一个含 2 n 2^n 2n 个整数的集合 A A A,我们需要计算: ∀ T ⊆ A \forall T\subseteq A ∀T⊆A, T T T 中所有元素 A i A_i Ai 之和, i i i 为元素对应下标,写为:
F m a s k = ∑ i ∈ m a s k A i F_{mask}=\sum_{i\in mask}A_i Fmask=i∈mask∑Ai
cpp
for(int i=0;i<(1<<N);i++)
f[i]=A[i];
for(int i=0;i<N;i++)
for(int mask=0;mask<(1<<N);mask++)
if(mask&(1<<i))f[mask]+=f[mask^(1<<i)];
光这么看肯定很难看出算法的精髓所在。应当搭配例题食用。
我的题单。
2.AT_arc100_c Or Plus Max
题意
有一个长度为 2 N 2^N 2N 的整数序列 A 0 , A 1 , . . . , A 2 N − 1 A_0,\ A_1,\ ...,\ A_{2^N-1} A0, A1, ..., A2N−1。(注意下标从 0 0 0 开始)
对于所有满足 1 ≤ K ≤ 2 N − 1 1\leq K\leq 2^N-1 1≤K≤2N−1 的整数 K K K,请解决以下问题:
- 设 i , j i,j i,j 为整数,满足 0 ≤ i < j ≤ 2 N − 1 0\leq i<j\leq 2^N-1 0≤i<j≤2N−1,且 ( i or j ) ≤ K (i\ \text{or}\ j)\leq K (i or j)≤K,求 A i + A j A_i+A_j Ai+Aj 的最大值。这里 o r or or 表示按位或运算。
1 ≤ N ≤ 18 1\leq N\leq 18 1≤N≤18, 1 ≤ A i ≤ 1 0 9 1\leq A_i\leq 10^9 1≤Ai≤109。
思路
首先 i ≠ j i\neq j i=j,我们发现 m a s k = i or j mask=i\ \text{or}\ j mask=i or j 的含义是, i , j i,j i,j 分别是 m a s k mask mask 的两个不同子集。
那么可以贪心地,对于每个 m a s k mask mask,求出其子集下标中的最大值 f m a s k f_{mask} fmask 以及次大值 g m a s k g_{mask} gmask。求法就是上面的求和改成维护最大值和次大值。
那么答案就是:
max m a s k ≤ K f m a s k + g m a s k \max_{mask\le K}f_{mask}+g_{mask} mask≤Kmaxfmask+gmask
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=23,S=1<<N;
ll n;
ll a[S];
ll f[S],g[S];//集合i子集最大和和次大和
int main()
{
scanf("%lld",&n);
for(int i=0;i<(1<<n);i++)
{
scanf("%lld",&a[i]);
f[i]=a[i];
}
for(int i=0;i<n;i++)
{
for(int mask=0;mask<(1<<n);mask++)
{
if(mask&(1<<i))
{
if(f[mask^(1<<i)]>f[mask])
{
g[mask]=f[mask];
f[mask]=f[mask^(1<<i)];
}
else if(f[mask^(1<<i)]>g[mask])g[mask]=f[mask^((1<<i))];
}
}
}
ll ans=0;
for(int i=1;i<(1<<n);i++)//ansk=max(1~k)
{
ans=max(ans,f[i]+g[i]);
printf("%lld\n",ans);
}
return 0;
}
3.CF165E Compatible Numbers
题意
如果两个整数 x x x 和 y y y 的按位与运算结果为 0 0 0,即 x & y = 0 x\ \&\ y=0 x & y=0,那么它们是兼容的。例如, 90 ( 101101 0 2 ) 90\ (1011010_2) 90 (10110102) 和 36 ( 10010 0 2 ) 36\ (100100_2) 36 (1001002) 是兼容的,因为 101101 0 2 & 10010 0 2 = 0 2 1011010_2\ \&\ 100100_2=0_2 10110102 & 1001002=02,而 3 ( 1 1 2 ) 3\ (11_2) 3 (112) 和 6 ( 11 0 2 ) 6\ (110_2) 6 (1102) 则不兼容,因为 1 1 2 & 11 0 2 = 1 0 2 11_2\ \&\ 110_2=10_2 112 & 1102=102。
给定一个整数数组 a 1 , a 2 , ... , a n a_1,a_2,\ldots,a_n a1,a2,...,an。你需要判断,对于每个数组元素,是否存在数组中的其它元素跟它兼容?如果存在兼容元素,还需要输出一个满足条件的元素。
1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1≤n≤106, 1 ≤ a i ≤ 4 × 1 0 6 1\le a_i\le 4\times 10^6 1≤ai≤4×106。
思路
对于一个数 a i a_i ai,对其二进制逐位取反得到 f l a p i flap_i flapi,即找 f l a p i flap_i flapi 子集中对应下标的最大值。维护 f m a s k f_{mask} fmask 表示 m a s k mask mask 子集内的下标最大值即可,答案就是 f f l a p i f_{flap_i} fflapi。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+9,M=22,S=5e6+9,inf=0x3f3f3f3f;
ll n;
ll a[N];
ll f[S];
ll flap(ll x)
{
return (~x)&((1<<M)-1);
}
int main()
{
scanf("%lld",&n);
memset(f,-1,sizeof(f));
for(int i=0;i<n;i++)
{
scanf("%lld",&a[i]);
f[a[i]]=i;
}
for(int i=0;i<M;i++)
{
for(int mask=0;mask<(1<<M);mask++)
{
if(mask&(1<<i))f[mask]=max(f[mask],f[mask^(1<<i)]);
}
}
for(int i=0;i<n;i++)
{
ll ret=f[flap(a[i])];
if(ret==-1)printf("-1 ");
else printf("%lld ",a[ret]);
}
return 0;
}