高维前缀和(SOS DP)

高维前缀和(SOS DP)

通常求二维前缀和,用容斥来求

但其实,完全可以先做一遍行的前缀和,再做一遍列的前缀和

拓展到 k 维也是如此,可以在 O(nk) 的复杂度求前缀和

但怎么和 DP 扯上关系?

可以把第 i 维当作阶段,每一维的具体信息是状态

先枚举阶段,表示当前固定其它维,只统计这一维的贡献,再枚举状态,根据是求前缀和还是后缀和转移

子集求和

它的思想可以用来解决子集求和类问题

如果想对所有 i[0,2n),求 fi=jiaj

一维一维的处理,枚举第 j 位,如果第 j 位是 1,就加上第 j 位不是 1f

其实 f 滚动掉了阶段这一维,原始的 DP 应是 dp(i,s) 为处理了状态为 s,二进制前 i 位的答案,第 i 位若为 1,可以在继承 dp(i1,s) 的基础上选择加上第 i 位为 0 的子集

超集同理,如果第 j 位是 0,就加上第 j 位是 1f

这里是超集的代码:

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大的信息 

min,max 同样可做

子集或超集都暗含着偏序关系,求前缀和也如此,每一维都要比当前的小,才可被加入当前的前缀和

Dirichlet 前缀和

可以在 O(nloglogn) 的复杂度内求出 1n 所有数因数/倍数的某些信息,例如,对每个数求出出现在给定集合中因数的个数

P5495 Dirichlet 前缀和

把每个数质因数分解,x=i=1kpiai,y=i=1kpibi

xy 产生贡献,当且仅当 i[1,k],aibi

本质就是高维前缀和,先枚举质数,再枚举具体是哪个数,注意从小到大枚举

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]];

应用

CF449D Jzzhu and Numbers

容斥的思想,与为 0 不好做,用全集 与不为 0 的情况,因为不为 0 说明指定的几位必须为 1,好处理

枚举指定哪几位为 1,如果指定的位数为奇数,容斥系数为 1,否则为 1

然后就是求超集的高维前缀和,fi=j[iaj]

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)

用类似后缀和的方法求出每个数出现在 a 序列中倍数的数量,记作 numi

考虑 DP,由于 gcd 变化时,后一个一定时前一个的因数

因此如果知道上一个 gcd i 以及下一个 gcd j,可以用 num 求出能摆的数有 numjnumi 个,因为是 i 倍数的一定也是 j 的倍数,是 i 倍数的在 i 处已经被用了,而且 i>j,在 i 处用更优

于是设 fi 表示末尾 gcdi 倍数时的最大贡献,枚举 i 的倍数 j 转移,fi=max{fj+i×(numinumj)}

初始时 fi=numi×i,转移从大到小

这样只能通过 Easy version

发现如果 j=p1×p2××pk×ip1,p2,pk 为质数,则 ji 的贡献被重复枚举

如果一次只多一个质数,例如 fj 先贡献给 fp2××pk×i,再贡献给 fp3××pk×i,这样转移去除了很多重复工作,且不会漏掉

于是每次枚举质数 pjfifi×pj 转移,复杂度同埃拉托色尼筛法,为 O(nloglogn)

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;
}
posted @   KellyWLJ  阅读(129)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示