高维前缀和学习笔记
高维前缀和
作用:
一般解决如下问题:
对于所有的 \(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;
}

浙公网安备 33010602011771号