高维前缀和学习笔记

高维前缀和

作用:

一般解决如下问题:

对于所有的 \(0\le i\le 2^n-1\),求解 \(\sum \limits_{j\subset i} a_j\)

这类问题枚举子集是 \(O(3^n)\) 的,用高维前缀和可以优化到 \(O(n2^n)\)

实现:

考虑二维前缀和的实现过程:

F(i,1,n) F(j,1,n) a[i][j]+=a[i-1][j];
F(i,1,n) F(j,1,n) a[i][j]+=a[i][j-1];

即运用到了每个维度分别相加的思想。

于是考虑根据这个思想扩展维度,不难写出 \(n\) 维代码:

F(j,0,n-1) F(i,0,(1<<n)-1) if(i>>j&1) f[i]+=f[i^(1<<j)];

这里 \(f\) 数组的含义是其子集上的数的和。

所以可以维护很多奇奇怪怪的东西比如子集上的最大最小值之类的。

超集

\(a\)\(b\) 的子集,则 \(b\)\(a\) 的超集。

F(j,0,n-1) F(i,0,(1<<n)-1) if(!(i>>j&1)) f[i]+=f[i^(1<<j)];

例题

[ARC100E] Or Plus Max

分析

发现我们可以对于每个 \(k\),求出最大的 \(a_i+a_j,i\mid j=k\),然后题目变成最大前缀和。

进一步转化为 \(i\mid j\subset k\)。然后对于每个 \(k\),维护其子集上的最大值和次大值。

思路

高维前缀和维护每个 \(k\) 子集上的最大值和次大值,输出每个 \(k\) 的答案时维护一个前缀和。

Code

#include<bits/stdc++.h>
#define sd std::
#define int long long
#define inf 0x3f3f3f3f
#define linf 1e18
#define il inline
#define db double
#define ldb long double
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define f(i,a,b) for(int i=(a);i>=(b);i--)
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define umap(x,y) sd unordered_map<x,y>
#define pque(x) sd priority_queue<x>
#define X first
#define Y second
#define kg putchar(' ')
#define Fr(a) for(auto it:a)
#define dbg(x) sd cout<<#x<<": "<<x<<sd endl
il int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
il void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
il void put(int x){print(x);putchar('\n');}
il void printk(int x){print(x);kg;}
const int N=(1<<18)+10;
int n;
pii a[N];//维护最大值和次大值 
pii merge(pii x,pii y)
{
	if(x.X>y.X) return {x.X,MAX(y.X,x.Y)};
	return {y.X,MAX(x.X,y.Y)};
}
il void solve()
{
	n=read();
	F(i,0,(1<<n)-1) a[i].X=read(),a[i].Y=-inf;
	F(j,0,n-1) F(i,0,(1<<n)-1) if(i>>j&1) a[i]=merge(a[i],a[i^(1<<j)]);
	int ans=0;
	F(k,1,(1<<n)-1)
	{
		ans=MAX(ans,a[k].X+a[k].Y);
		put(ans);
	}
}
signed main()
{
	int T=1;
//	T=read();
	while(T--) solve();
    return 0;
}

[CF1208F] Bits And Pieces

考虑枚举 \(d_i\)

容易想到按位贪心,从高到低位考虑。

考虑维护一个数 \(x\)

从高位到低位取 \(j\) 位:

  • 若存在两个 \(d_j\)\(d_k\)\(j,k>i\))都包含 \(x+2^j\),则可以取,\(x\gets x+2^j\)
  • 否则不行。

在枚举位数的时候,若 \(d_i\) 上有这一位就可以直接加上了。

对于这个问题,可以考虑sos dp,处理一下 \(d_j\subset i\)\(j\) 的最大和次大两个下标,没有就是 -1

然后需要判断次大下标是否 \(>i\)

Prefix XORs

先考虑 \(a\)\(k\) 阶前缀和 \(S_{k}\) 是多少。

考虑 \(a_p\) 的影响。设 \(f_{i,j}\) 表示 \(i\) 阶前缀和下,\(S_{k,j}\)\(a_p\) 的贡献系数。

一维即 \(f_{i,j\ge p}=1\)。转移显然是 \(f_{i,j}=f_{i-1,j}+f_{i,j-1}\)

考虑组合意义。发现 \(f_{i,j}\) 就是从网格左上角 \((1,p)\) 走到右下角 \((i,j)\) 的方案数。

计算这个是很容易的,一共要走 \((i-1)+(j-p)=i+j-1-p\) 步,其中要选任意 \(i-1\) 步向右,方案数就是 \(\binom{i+j-1-p}{i-1}\)

所以,\(S_{k,i}=\sum \limits_{1\le j\le i}\binom{k+i-j-1}{k-1}a_j\)


带到本题,即 \(S_{k,n}=\bigoplus \limits_{i=1}^n F(a_i,\binom{k+n-i-1}{k-1})\)。其中 \(F(x,y)\) 表示 \(x\) 异或 \(y\) 次的值。

显然 \(\binom{k+i-j-1}{k-1}\) 为奇数才会有贡献。

考虑 \(\binom{n}{m}\) 什么时候才会为奇数。

根据 lucas 定理,\(\binom{n}{m}=\binom{\lfloor\frac{n}{2}\rfloor}{\lfloor\frac{m}{2}\rfloor}\times \binom{n\bmod 2}{m \bmod 2} \pmod 2\)

则相当于是将 \(n\)\(m\) 的二进制位上对应的 \(\binom{n_i}{m_i}\) 的积。

则相当于这些 \(\binom{n_i}{m_i}\) 中不能有偶数。

显然,\(\binom{1}{1}=1,\binom{1}{0}=1,\binom{0}{0}=1,\binom{0}{1}=0\),只有最后一个不合法。

即,\(m\) 必须被 \(n\) 包含。

枚举 \(k\gets k-1\),即 \(k+n-i\)\(k\) 包含的时候有一个 \(a_i\) 的贡献。

\(k+p\)\(p\) 包含等价于 \(k\)\(p\) 之间无交。即 \(k\)\(n-i\) 无交的时候有一个 \(a_i\) 的贡献。

不妨将原序列逆序后从 \(0\) 开始排列,即 \(k\)\(i\) 无交的时候有一个 \(a_i\) 的贡献。

然后就很简单了,处理 \(k\) 补集的子集异或和即可。

#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=3e6+10;
int n,q,f[N];//高维前缀和
int m=20;
void solve()
{
	n=read();q=read();
	F(i,1,n) f[n-i]=read();
	F(j,0,m-1) F(i,0,(1<<m)-1) if(i>>j&1) f[i]^=f[i^(1<<j)];
	F(i,0,q-1) printk(f[(~i)&((1<<m)-1)]);
}
int main()
{
	int T=1;
//	T=read();
	while(T--) solve();
    return 0;
}
posted @ 2024-12-04 21:36  _E_M_T  阅读(35)  评论(0)    收藏  举报