sosdp
子集 dp
sosdp,高维前缀和。
传统的,计算二维前缀和的递推式为:
si,j←si−1,j+si,j−1−si−1,j−1s_{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
这可以等价于,每一维上,这个点加上该维上一个点的和。我们不妨把它拓展到 nnn 维,用二进制位表示高维。
1.高维前缀和
给定一个含 2n2^n2n 个整数的集合 AAA,我们需要计算:∀T⊆A\forall T\subseteq A∀T⊆A,TTT 中所有元素 AiA_iAi 之和,iii 为元素对应下标,写为:
Fmask=∑i∈maskAiF_{mask}=\sum_{i\in mask}A_iFmask=i∈mask∑Ai
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
题意
有一个长度为 2N2^N2N 的整数序列 A0,A1,...,A2N−1A_0,\ A_1,\ ...,\ A_{2^N-1}A0, A1, ..., A2N−1。(注意下标从 000 开始)
对于所有满足 1≤K≤2N−11\leq K\leq 2^N-11≤K≤2N−1 的整数 KKK,请解决以下问题:
- 设 i,ji,ji,j 为整数,满足 0≤i<j≤2N−10\leq i<j\leq 2^N-10≤i<j≤2N−1,且 (ior j)≤K(i\ \text{or}\ j)\leq K(i or j)≤K,求 Ai+AjA_i+A_jAi+Aj 的最大值。这里 ororor 表示按位或运算。
1≤N≤181\leq N\leq 181≤N≤18,1≤Ai≤1091\leq A_i\leq 10^91≤Ai≤109。
思路
首先 i≠ji\neq ji=j,我们发现 mask=ior jmask=i\ \text{or}\ jmask=i or j 的含义是,i,ji,ji,j 分别是 maskmaskmask 的两个不同子集。
那么可以贪心地,对于每个 maskmaskmask,求出其子集下标中的最大值 fmaskf_{mask}fmask 以及次大值 gmaskg_{mask}gmask。求法就是上面的求和改成维护最大值和次大值。
那么答案就是:
maxmask≤Kfmask+gmask\max_{mask\le K}f_{mask}+g_{mask}mask≤Kmaxfmask+gmask
代码
#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
题意
如果两个整数 xxx 和 yyy 的按位与运算结果为 000,即 x&y=0x\ \&\ y=0x & y=0,那么它们是兼容的。例如,90(10110102)90\ (1011010_2)90 (10110102) 和 36(1001002)36\ (100100_2)36 (1001002) 是兼容的,因为 10110102&1001002=021011010_2\ \&\ 100100_2=0_210110102 & 1001002=02,而 3(112)3\ (11_2)3 (112) 和 6(1102)6\ (110_2)6 (1102) 则不兼容,因为 112&1102=10211_2\ \&\ 110_2=10_2112 & 1102=102。
给定一个整数数组 a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,…,an。你需要判断,对于每个数组元素,是否存在数组中的其它元素跟它兼容?如果存在兼容元素,还需要输出一个满足条件的元素。
1≤n≤1061\le n\le 10^61≤n≤106,1≤ai≤4×1061\le a_i\le 4\times 10^61≤ai≤4×106。
思路
对于一个数 aia_iai,对其二进制逐位取反得到 flapiflap_iflapi,即找 flapiflap_iflapi 子集中对应下标的最大值。维护 fmaskf_{mask}fmask 表示 maskmaskmask 子集内的下标最大值即可,答案就是 fflapif_{flap_i}fflapi。
代码
#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;
}