高维前缀和 简记

对于前缀和,一维求法如下:

for(int i=1;i<=n;i++) a[i]+=a[i-1];

而二维前缀和,一般我们是用容斥思想去求的,有 \(f_{i,j}=a_{i,j}+f_{i-1,j}+f_{i,j-1}-f_{i-1,j-1}\)

但是也可以分行、列讨论,先处理出每一行的前缀和,再对它求每一列的前缀和。因为求列的前缀和之前这里的值已经是这一行的前缀和了,再求列的前缀和就是左上角的矩阵,也就是二维前缀和。

for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++) a[i][j]+=a[i][j-1];
}
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++) a[i][j]+=a[i-1][j];
}

而对于更高维的前缀和,我们也可以这样按每一维去讨论。如三维前缀和:

for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++) 
	{
		for(int k=1;k<=n;k++) a[i][j][k]+=a[i][j-1][k];
	}
}
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++) 
	{
		for(int k=1;k<=n;k++) a[i][j][k]+=a[i][j][k-1];
	}
}
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=n;j++) 
	{
		for(int k=1;k<=n;k++) a[i][j][k]+=a[i-1][j][k];
	}
}

那么这个东西有没有什么其他作用?在 \(k\) 进制下,一个数可以表示为一个由 \(k\) 进制下各位数构成的高维坐标,而如果要对每一位都小于等于它这一位的所有数求和就可以用高维前缀和。例如, \(6=(110)_2\),于是可以把 \(6\) 看成 \(3\) 维坐标 \((1,1,0)\)

而最常用的就是二进制下求子集和了。

如果直接去枚举所有子集,时间复杂度 \(O(3^n)\),而如果用高维前缀和,只需 \(O(n2^n)\)

具体怎么做呢?我们按位一位一位考虑,也就相当于高维前缀和中一维一维考虑。设 \(dp_{j,i}\) 表示考虑完了第 \(j\) 位,坐标集合为 \(i\) 的子集和。

  • 如果 \(i\) 的第 \(j\) 位是 \(0\),则有 \(dp_{j,i}=dp_{j-1,i}\)
  • 如果 \(i\) 的第 \(j\) 位是 \(1\),则有 \(dp_{j,i}=dp_{j,i-1}\)

而发现可以滚动,甚至可以直接压成一维的。所以有:

  • 如果 \(i\) 的第 \(j\) 位是 \(0\),则不转移。
  • 如果 \(i\) 的第 \(j\) 位是 \(1\),则有 \(dp_{i}=dp_{i-2^{j}}\)

初始化 \(dp_{i}=w_i\)

for(int j=0;j<n;j++)
{
	for(int i=1;i<(1<<n);i++) if(i&(1<<j)) dp[i]+=dp[i^(1<<j)];
}

最后附带一个题单

posted @ 2024-12-26 15:09  Twilight_star  阅读(32)  评论(0)    收藏  举报