首页 > 技术文章 > 高维前缀和

EricQian 2022-03-01 12:51 原文

高维前缀和

主要内容

昨天(\(\texttt{2022.2.28}\))打 ARC 的 D 题时,恍然发现我不会高维前缀和,匆匆来学一下。

比如二维前缀和 \(s_{i,j}\) 表示在一个二维平面上从 \((1,1)\)\((i,j)\) 的所有点的权值之和,我们定义高维前缀和 \(s_{p_1,p_2,p_3,\cdots,p_n}\) 表示所有 \((q_1,q_2,q_3,\cdots,q_n)\) 并且满足 \(q_1\le p_1,q_2\le p_2,\cdots,q_n\le p_n\) 的所有点的权值之和。

如何求解高维前缀和呢?

举个例子,二维前缀和可以用一下代码来求:

for(int i=1;i<=n;i++)
	 for(int j=1;j<=m;j++)
	 	 sum[i][j]+=sum[i-1][j];
for(int i=1;i<=n;i++)
	 for(int j=1;j<=m;j++)
	 	 sum[i][j]+=sum[i][j-1];

类比一下,高维前缀和可以用如下方式求解:

// 假设前缀和是 q 进制的。 
int val[N]={1,q,q^2,q^3,...,q^{N-1}};
for(int opt=1;opt<=N;opt++)
	 for(int i=0;i<=MAX;i++)
	 	 if((i/val[opt])%q>=1)
	 	 	 sum[i]+=sum[i-val[opt]];

每一位都从这一位减一转移过来,其实高维前缀和也可以被想为一个 Dp 的过程,类似 Dp 的转移更好理解。

例题

CF772D Varying Kibibits

定义 \(f(S)\) 表示一个由若干个数组成的集合 \(S\) 中,返回值十进制每一位上的数为所有数中这一位的最小值。

给定 \(n\) 个数组成的集合 \(T\),求:

\[\oplus_{x=0}^{999999}\left(x\times\left(\left(\sum_{S\subseteq T,S\not=\emptyset,f(S)=x}\left(\sum_{y\in S}y\right)^2\right)\bmod 1000000007\right)\right) \]

\(n\le 10^6,t_i< 10^6\)

想到高维前缀和之后,难点就是怎样从当前这一位比它大 \(1\) 的位置转移过来。

主要问题是平方和的合并问题,发现其实就是这样一个过程:

\[a_1^2+a_2^2~\texttt{add}~b_1^2+b_2^2+b_3^2\Rightarrow(a_1+b_1)^2+(a_1+b_2)^2+(a_1+b_3)^2+(a_1+b_1)^2+(a_2+b_2)^2+(a_2+b_3)^2 \]

这样好办了,在每一位记下所有集合的 \(sum\) 的和、所有集合的 \(sum^2\) 的和与集合数量,直接转移即可。

发现这样求出来的是 \(f(x)\) 大于等于它的答案,所以最后还要进行一次差分。

\(\texttt{code}\)

[ARC100C] Or Plus Max

直接维护最大和第二大的即可。

CF1208F Bits And Pieces

给定 \(n\) 个数的数组 \(d\),找到 \(i<j<k\)\(i,j,k\),使得 \(d_i|(d_j \& d_k)\) 最大。

\(3\le n\le 10^6,0\le d_i\le 2\times 10^6\)

考虑枚举每一位的 \(d_i\),预处理可行的 \(\infty\oplus d_i\) 的子集的最大值(如果 \(d_i\) 这一位为 \(1\),答案肯定有 \(1\))。

但是好像假了,去看题解了。。

\(\bigstar\texttt{Hint-1}\):我们可以考虑计算出 \(d_i\) 部分包含的最小的 \(i\)\(d_j\&d_k\) 部分包含的最大的 \(j\)

上边的可以用高维前缀和快速搞定,之后好像有不会了。。

\(\bigstar\texttt{Hint-2}\):之后发现答案是从高到第决定的,只用维护第 \(i\) 位为 \(1\) 的最大值即可,从高位到低位判断这一位是否为 \(1\),每次 \(\mathcal{O(n)}\) 验证,这样压缩到了 \(\mathcal{O(maxbit\times n)}\)。(验证部分代码如下)

inline bool check(int x)
{
	 for(int i=0;i<=MAX;i++)
	 	 if(dp[i].mini<dp[x^(x&i)].maxk) return true;
	 return false;
}

这道题都要看两个 hint,我太菜了呀!!

CF449D Jzzhu and Numbers

给定长为 \(n\) 的序列 \(\{a\}\),计算从中选出一些数使得它们与起来等于 \(0\) 的方案数。

\(n,a_i\le 10^6\)

有一个牛逼的状态:\(dp(s)\) 表示选出的数与起来为 \(s\) 的超集的方案数

发现其实不用一开始就将方案数算出来,可以记下可以选择的数的数量,那么方案数直接就是 \(2^{dp(s)}\)

那么计算出超集后再容斥就是答案了。

\(\bigstar\texttt{important}\):记得减去空集的情况,因为每次计算都存在空集。

n=rd();
for(int i=1,x;i<=n;i++) x=rd(),dp[x]++;
for(int p=1;p<=20;p++)
	 for(int i=0;i<=1000000;i++)
	 	 if(!(i & (1<<(p-1))))
	 	 	 dp[i]+=dp[i+(1<<(p-1))];
for(int i=0;i<=1000000;i++)
{
	 if(__builtin_popcount(i)&1) ans=(ans-ksm(2,dp[i])+1+mod)%mod;
	 else ans=(ans+ksm(2,dp[i])-1)%mod;
}
printf("%d\n",ans%mod);

推荐阅读