高维前缀和(SOS DP)
高维前缀和(SOS DP)
通常求二维前缀和,用容斥来求
但其实,完全可以先做一遍行的前缀和,再做一遍列的前缀和
拓展到 维也是如此,可以在 的复杂度求前缀和
但怎么和 DP 扯上关系?
可以把第 维当作阶段,每一维的具体信息是状态
先枚举阶段,表示当前固定其它维,只统计这一维的贡献,再枚举状态,根据是求前缀和还是后缀和转移
子集求和
它的思想可以用来解决子集求和类问题
如果想对所有 ,求
一维一维的处理,枚举第 位,如果第 位是 ,就加上第 位不是 的 值
其实 滚动掉了阶段这一维,原始的 DP 应是 为处理了状态为 ,二进制前 位的答案,第 位若为 ,可以在继承 的基础上选择加上第 位为 的子集
超集同理,如果第 位是 ,就加上第 位是 的 值
这里是超集的代码:
for(ll j = 0; j < 20; ++j) // 高维前缀和,超集
for(ll i = mx; i >= 0; --i) // f[i] 为包含 i的和(有多少个数 & i = i)
if(!((i >> j) & 1)) f[i] += f[i ^ (1ll << j)];
// 倒序枚举:DP数组其实压了一维,用到了比 i大的信息
同样可做
子集或超集都暗含着偏序关系,求前缀和也如此,每一维都要比当前的小,才可被加入当前的前缀和
Dirichlet 前缀和
可以在 的复杂度内求出 所有数因数/倍数的某些信息,例如,对每个数求出出现在给定集合中因数的个数
把每个数质因数分解,
则 对 产生贡献,当且仅当
本质就是高维前缀和,先枚举质数,再枚举具体是哪个数,注意从小到大枚举
for(ui j = 1; j <= cnt; ++j)
for(ui i = 1; i * prime[j] <= n; ++i) a[i * prime[j]] += a[i];
求倍数的:
for(ll j = 1; j <= cnt; ++j)
for(ll i = V / prime[j]; i > 0; --i) num[i] += num[i * prime[j]];
应用
容斥的思想,与为 不好做,用全集 与不为 的情况,因为不为 说明指定的几位必须为 ,好处理
枚举指定哪几位为 ,如果指定的位数为奇数,容斥系数为 ,否则为
然后就是求超集的高维前缀和,
int main()
{
n = read(), pw[0] = 1;
for(ll i = 1; i <= n; ++i) a[i] = read(), ++f[a[i]];
for(ll i = 1; i <= n; ++i) pw[i] = add(pw[i - 1], pw[i - 1]);
for(ll j = 0; j < 20; ++j) // 高维前缀和,超集
for(ll i = mx; i >= 0; --i) // f[i] 为包含 i的和(有多少个数 & i = i)
if(!((i >> j) & 1)) f[i] += f[i ^ (1ll << j)]; // 倒序枚举:DP数组其实压了一维,用到了比 i大的信息
for(ll i = 1; i <= mx; ++i)
if(popcnt(i) & 1) ans = add(ans, add(pw[f[i]], mod - 1));
else ans = add(ans, add(mod - pw[f[i]], 1));
printf("%lld", add(add(pw[n], mod - 1), mod - ans)); // 容斥,总方案数-&至少有一位+&至少有两位……
return 0;
}
CF1614D2 Divan and Kostomuksha (hard version)
用类似后缀和的方法求出每个数出现在 序列中倍数的数量,记作
考虑 DP,由于 变化时,后一个一定时前一个的因数
因此如果知道上一个 以及下一个 ,可以用 求出能摆的数有 个,因为是 倍数的一定也是 的倍数,是 倍数的在 处已经被用了,而且 ,在 处用更优
于是设 表示末尾 为 倍数时的最大贡献,枚举 的倍数 转移,
初始时 ,转移从大到小
这样只能通过 Easy version
发现如果 , 为质数,则 对 的贡献被重复枚举
如果一次只多一个质数,例如 先贡献给 ,再贡献给 ,这样转移去除了很多重复工作,且不会漏掉
于是每次枚举质数 , 从 转移,复杂度同埃拉托色尼筛法,为
int main()
{
read(n);
for(ll i = 1; i <= n; ++i) read(a[i]), ++num[a[i]];
for(ll i = 2; i <= V; ++i)
{
if(!st[i]) prime[++cnt] = i;
for(ll j = 1; j <= cnt && i * prime[j] <= V; ++j)
{
st[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
for(ll j = 1; j <= cnt; ++j)
for(ll i = V / prime[j]; i > 0; --i) num[i] += num[i * prime[j]];
for(ll i = 1; i <= V; ++i) f[i] = i * num[i];
for(ll i = V; i > 0; --i)
{
for(ll j = 1; j <= cnt && i * prime[j] <= V; ++j)
f[i] = max(f[i], f[i * prime[j]] + i * (num[i] - num[i * prime[j]]));
if(num[i] == n) ans = max(ans, f[i]);
}
cout << ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】