算法随笔——高维前缀和

高维前缀和

首先考虑最普通的一维前缀和,

可以写成这样:

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

二维也很简单:

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

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

仿照这样我们可以写出 \(n\) 维前缀和。

for (i:1~n)
    for(j:1~n)
        for (k:1~n)
            for (l:1~n)
                for(m:1~n)
                    sum[i][j][k][l][m] += sum[i][j][k][l][m-1];
for (i:1~n)
    for(j:1~n)
        for (k:1~n)
            for (l:1~n)
                for(m:1~n)
                    sum[i][j][k][l][m] += sum[i][j][k][l-1][m];
...

但这样显然过于暴力了。

我们可以考虑将数组对应的下标压缩成一个数,然后用类似状压dp的过程优化。

for (int i = 0;i < n;i++) // 第一层枚举对应上面的第几个循环
    for (int j = 0;j < (1<<n);j++) // 第二层枚举对应上面枚举 i,j,k...,注意这里的下标只有 0,1
        if (j >> i & 1)
            f[j] += f[j ^ (1 << i)]; 
//时间复杂度 O(n*2^n) 
        
    

[ARC100E] Or Plus Max

题意:对于每个 \(k\)

\[\max \{ a_i+a_j \} (i | j \le k) \]

容易想到只需要求出 \(i|j = k\) 的答案即可,我们可以发现 \(i,j\) 一定是 \(k\) 的子集。

那我们就可以想到一个算法,高维前缀和算每个 \(k \in [0,2^n-1]\) 的子集最大值和次大值,然后相加即可。

但这似乎不太对,因为 \(i|j < k\) 的某些答案也算进去了,但这实际上没有影响,因为答案本来就包含这些贡献,因此就做完了。

模版题。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define PII pair<int,int>
#define rep(k,a,b) for (int k = a;k <= b;k++)
#define mem memset
#define rd read
int read()
{
	int f=1,k=0;char c = getchar();
	while(c <'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')k=(k<<1)+(k<<3)+(c^48),c=getchar();
	return k*f;
}

const int N = 2e6+5;

int n;
int a[N];

PII f[N];


void update(PII &a,PII b)
{
	auto [mx1,mx2] = b;
	if (mx1 > a.first) a.second = a.first,a.first = mx1;
	else a.second = max(a.second,mx1);
	if (mx2 > a.first) a.second = a.first,a.first = mx2;
	else a.second = max(a.second,mx2);
}


int main()
{
	cin >> n;
	rep(i,0,(1<<n)-1)
		a[i] = rd(),f[i] = {a[i],0};
	rep(i,0,n-1)
		rep(e,0,(1<<n)-1)
			if (e >> i & 1) 
				update(f[e],f[e^(1<<i)]);

	int ans = 0;
	rep(k,1,(1<<n)-1)
	{
		auto[mx1,mx2] = f[k];
		ans = max(mx1+mx2,ans);
		cout << ans << endl;
	}
	
	return 0;
}

求超集

一样的。

for (int i = 0;i < n;i++) // 第一层枚举对应上面的第几个循环
    for (int j = 0;j < (1<<n);j++) // 第二层枚举对应上面枚举 i,j,k...,注意这里的下标只有 0,1
        if (!(j >> i & 1)) //只需要修改这里
            f[j] += f[j ^ (1 << i)]; 
//时间复杂度 O(n*2^n) 

子集反演

\[f(S) = \sum_{S\subseteq T} g(T) \Longleftrightarrow g(S) = \sum_{S\subseteq T} f(T) \times (-1)^{|S| - |T|}\]

\[f(S) = \sum_{T\subseteq S} g(T) \Longleftrightarrow g(S) = \sum_{T \subseteq S} f(T) \times (-1)^{|S| - |T|}\]

posted @ 2024-09-23 21:48  codwarm  阅读(49)  评论(0)    收藏  举报