首页 > 其他分享 >高维前缀和(SOS DP)

高维前缀和(SOS DP)

时间:2024-02-14 22:33:41浏览次数:18  
标签:前缀 ll 枚举 times SOS num DP 高维

高维前缀和(SOS DP)

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

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

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

但怎么和 DP 扯上关系?

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

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

子集求和

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

如果想对所有 \(i\in[0,2^n)\),求 \(f_i=\sum_{j\subseteq i}a_j\)

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

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

超集同理,如果第 \(j\) 位是 \(0\),就加上第 \(j\) 位是 \(1\) 的 \(f\) 值

这里是超集的代码:

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(n\log\log n)\) 的复杂度内求出 \(1\sim n\) 所有数因数/倍数的某些信息,例如,对每个数求出出现在给定集合中因数的个数

P5495 Dirichlet 前缀和

把每个数质因数分解,\(x=\sum_{i=1}^k p_i^{a_i},y=\sum_{i=1}^k p_i^{b_i}\)

则 \(x\) 对 \(y\) 产生贡献,当且仅当 \(\forall i\in[1,k],a_i\le b_i\)

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

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\)

然后就是求超集的高维前缀和,\(f_i=\sum_{j}[i\subseteq a_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)

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

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

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

于是设 \(f_i\) 表示末尾 \(\gcd\) 为 \(i\) 倍数时的最大贡献,枚举 \(i\) 的倍数 \(j\) 转移,\(f_i=\max\{f_j+i\times(num_i-num_j)\}\)

初始时 \(f_i=num_i\times i\),转移从大到小

这样只能通过 Easy version

发现如果 \(j=p_1\times p_2\times\dots\times p_k\times i\),\(p_1,p_2,\dots p_k\) 为质数,则 \(j\) 对 \(i\) 的贡献被重复枚举

如果一次只多一个质数,例如 \(f_j\) 先贡献给 \(f_{p_2\times\dots\times p_k\times i}\),再贡献给 \(f_{p_3\times\dots\times p_k\times i}\),这样转移去除了很多重复工作,且不会漏掉

于是每次枚举质数 \(p_j\),\(f_i\) 从 \(f_{i\times p_j}\) 转移,复杂度同埃拉托色尼筛法,为 \(O(n\log\log n)\)

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

标签:前缀,ll,枚举,times,SOS,num,DP,高维
From: https://www.cnblogs.com/KellyWLJ/p/18015746

相关文章

  • TCP和UDP面试题提问
    @目录TCPUDP总结应用TCP(传输控制协议)和UDP(用户数据报协议)是两种计算机网络通信协议,它们在网络通信中起着不同的作用。TCPTCP是面向连接的协议,它在数据传输之前需要在发送端和接收端建立一条连接。TCP提供可靠的数据传输,它使用确认和重传机制来确保数据的可靠性和完整性。T......
  • P1941-DP【绿】
    题目本身只是一道有些难度的普通dp题,题解中有人说可以把这个看作是背包,我不是这么做的便没细看,感觉能把他联想为背包问题的特例的人的发散思维能力真强。不过倒也没必要,常规做即可,用二维数组即可描述状态,dp[i][j]表示只由前i个横向单位长度组成的游戏中以(i,j)结尾游戏所需的最小游......
  • 数位 DP 做题记录
    数位DP数位DP的常见套路就是记录当前到哪一位,是否抵着上界,转移时枚举当前可以填哪些数,做一遍记忆化搜索。P3413SAC#1-萌数题意:求\([l,r]\)中有多少个数中含有回文子串。思路:如果存在回文子串,那么必然有相邻两位相同或者间隔一位相同,在数位DP时额外记录前2位就可以......
  • Codeforces Round 113 (Div. 2)E. Tetrahedron(dp、递推)
    目录题面链接题意题解代码总结题面链接E.Tetrahedron题意从一个顶点出发走过路径长度为n回到出发点的方案总数题解考虑dp\(f[i][0|1|2|3]\):走了i步,现在在j点的方案总数转移:\(f[i][0]=f[i-1][1]+f[i-1][2]+f[i-1][3]\)\(f[i][1]=f[i-1][0]+f[i-1][2]+f[i-1][3]\)\(f......
  • Atcoder Educational DP Contest 题解
    比赛链接:Atcoderr或者题单A:Frog1常规线性dp,注意到对于每个落点来说,青蛙只能由第\(i-1\)个石头或者第\(i-2\)个石头转移得到,那么我们让\(dp[i]\)为当跳到第\(i\)个石头上的最小费用。此时显然有:\[dp_i=\min(dp_{i-1}+\left|h_i-h_{i-1}\right|,dp_{i-2}+\left|h_......
  • 【Java 并发】【队列应用】【二】Tomcat的NioEndPoint中ConcurrentLinkedQueue 的使用
    1 前言这一节我们讲解Tomcat的NioEndPoint中ConcurrentLinkedQueue的使用。2  Tomcat的容器结构本节讲解apache-tomcat-7.0.32-src源码中ConcurrentLinkedQueue的使用。首先介绍Tomcat的容器结构以及NioEndPoint的作用,以便后面能够更加平滑地切入话题,如图11-4所示......
  • POJ--1179 Polygon(区间DP)
    记录22:012024-2-10http://poj.org/problem?id=1179区间DP问题。区间DP问题可能需要注意的点就是是根据区间长度来计算的,随着迭代区间长度不断增加,结果也就计算出来了这种“任意选择一个位置断开,复制形成2倍长度的链”的方法,是解决DP中环形结构的常用手段之一因此读入数......
  • JMeter 进行UDP压力测试
    第一步:安装udp插件第二步:添加线程组,然后按下添加UDP请求设置如下配置你要测试的服务器IP和端口。按照下面的格式输入16进制数数据然后可以开始跑了......
  • Codeforces Round 260 (Div. 1)A. Boredom(dp)
    最开始写了一发贪心wa了,然后这种选和不选的组合优化问题,一般是考虑动态规划\(dp[i][0]:\)表示第i个数不选的最大值\(dp[i][1]:\)表示第i个数选的最大值考虑转移:\(dp[i][0]=max(dp[i-1][1],dp[i-1][0])\)\(dp[i][1]=dp[i-1][1]+a[i]*i\)需要将每一个数用一个桶统计次数因......
  • 基于Huggingface Accelerate的DDP训练
    #-*-coding:utf-8-*-""""ThisdocumentisasimpleDemoforDDPImageClassification"""fromtypingimportCallablefromargparseimportArgumentParser,Namespaceimporttorchfromtorch.backendsimportcudnnfro......