首页 > 其他分享 >[笔记]数位dp例题及详解-下

[笔记]数位dp例题及详解-下

时间:2024-04-13 22:13:11浏览次数:25  
标签:sta int 30 pos 例题 优化 dp 数位

【接上回】-数位dp例题及详解-上
共\(4\)道难度较高、较有思考性的题。
附上数位dp题单:https://www.luogu.com.cn/training/494976#problems

小小的总述
数位dp是这样的,状态表示越简洁,dp数组越小巧,进而时空消耗就越少。所以我们刷题的时候,可以先无脑把\(f\)数组的每一维都设为与当前状态相关的所有变量,然后在此基础上,再用下面的方法进行优化:

  • 逐步思考哪些状态是一样的,进而优化维度或者每一维的大小。(最重要,是数位dp的精髓,可以同时优化时间和空间)
    例:几乎所有数位dp题
  • 搜索中途可能有一些状态需要剪枝。(只能优化时间,记忆化越强效果越不明显,所以其实也不是那么重要,但是应该作为写搜索的一个习惯)
    例:Round Numbers SSegment Sum
  • 思考dp数组有没有冗余空间(根本搜索不到的那种)。(只能优化空间,可应对一些卡常的题)
    例:Balanced Numbers

SP10606 Balanced Numbers

SPOJ注册不上所以暂时无法提交w,但是3份代码与正解对拍没有问题。

使用\(vis[0\sim 9]\)表示\(0\sim 9\)的访问情况,\(sta[0\sim 9]\)表示\(0\sim 9\)填写个数的奇偶性(奇数为\(1\),偶数为\(0\))。暴搜先打出来,然后考虑怎么记忆化。我们发现如果两个状态(\(limit=false\))填写到同一位置\(pos\),而且\(vis\)和\(sta\)都相同,那么这两个状态答案相同。

所以用\(f[pos][vis][sta]\)来记忆化,空间\(20*1024*1024\),不会MLE(1.46G的内存)

注意到数据范围,可能需要开unsigned long long,注意这样\(f\)数组就不能初始化为\(-1\)了,可以再开一个bool类型的\(fv\)表示\(f\)的这个状态是否计算出答案了。

1.1 Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30];
bitset<10> vis,sta;
bool fv[30][1024][1024];
int f[30][1024][1024];
int dfs(int pos,bool limit,bool zero){
	if(pos==0){
		for(int i=0;i<=9;i++) if(vis[i]&&sta[i]==i%2) return 0;
		return 1;
	}
	int numvis=vis.to_ullong(),numsta=sta.to_ullong();
	if(!limit&&!zero&&fv[pos][numvis][numsta])
		return f[pos][numvis][numsta];
	int rig=limit?a[pos]:9,ans=0;
	for(int i=0;i<=rig;i++){
		bool is=(zero&&i==0);
		bool tvis=vis[i],tsta=sta[i];
		if(!is) vis[i]=1,sta[i]=!sta[i];
		ans+=dfs(pos-1,limit&&i==rig,is);
		vis[i]=tvis,sta[i]=tsta;
	}
	if(!limit&&!zero) f[pos][numvis][numsta]=ans,fv[pos][numvis][numsta]=1;
	return ans;
}
int solve(int x){
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1);
}
signed main(){
	memset(fv,0,sizeof fv);
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

空间优化

(第3种优化方式)

其实上面的就能过了,但是我们注意到还有优化空间。

上面的表示其实就是四进制,但是我们发现\(vis[i]=0,sta[i]=1\)的情况不存在,所以我们可以优化成三进制,状压一下就可以了。总空间\(20*(3^{10})=20*59049\)。

按道理说应该只是优化了空间而没有优化时间,因为上面所说的情况根本不会搜索到。

(但很奇怪的是这份代码跑得奇快,具体见下面的时间对比,如果大家有解答请在评论区告诉我,谢谢!)

1.2 空间优化Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30];
int sta[10];
bool fv[30][59049];
int f[30][59049];
//0没访问,1访问奇数次,2访问偶数次,10位三进制
//优化掉了“没访问过,奇数次”的状态
int to_num(){
	int ans=0;
	for(int i=0;i<=9;i++) ans=ans*3+sta[i];
	return ans;
}
int dfs(int pos,bool limit,bool zero){
	if(pos==0){
		for(int i=0;i<=9;i++){
			if(sta[i]==0) continue;
			if(sta[i]-1!=i%2) return 0;
		}
		return 1;
	}
	int numsta=to_num();
	if(!limit&&!zero&&fv[pos][numsta])
		return f[pos][numsta];
	int rig=limit?a[pos]:9,ans=0;
	for(int i=0;i<=rig;i++){
		bool is=(zero&&i==0);
		int tsta=sta[i];
		if(!is) sta[i]=(sta[i]==0||sta[i]==2)?1:2;
		ans+=dfs(pos-1,limit&&i==rig,is);
		sta[i]=tsta;
	}
	if(!limit&&!zero) f[pos][numsta]=ans,fv[pos][numsta]=1;
	return ans;
}
int solve(int x){
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1);
}
signed main(){
	memset(fv,0,sizeof fv);
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

究极时空优化

(第1种优化方式)

结论:只要vis奇数位上1的个数vis偶数位上1的个数sta奇数位上1的个数sta偶数位上1的个数都分别相等,两种状态答案就一样。所以可以直接使用\(f[pos][a][b][c][d]\)来记忆化,也可以直接压缩成\(f[pos][a]\)。空间\(20*(6^4)=20*1296\),时间也是。

为什么呢?如果你一共访问了\(n\)个奇数,其中有\(m(m\leq n)\)个奇数访问了奇数次。那么不用管具体这些奇数是几,因为结果中奇数互相换是不会影响的。比如\(331132377\)满足条件,那么我把\(1\)和\(7\)互换,或者把\(3\)都换成\(9\)……都不会影响结果。偶数同理。

1.3 究极时空优化Code
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int t,l,r,a[30];
bitset<10> vis,sta;
bool fv[30][1296];
int f[30][1296];
int dfs(int pos,bool limit,bool zero){
	if(pos==0){
		for(int i=0;i<=9;i++) if(vis[i]&&sta[i]==i%2) return 0;
		return 1;
	}
	int num=(vis[0]+vis[2]+vis[4]+vis[6]+vis[8]);
	num=num*6+(vis[1]+vis[3]+vis[5]+vis[7]+vis[9]);
	num=num*6+(sta[0]+sta[2]+sta[4]+sta[6]+sta[8]);
	num=num*6+(sta[1]+sta[3]+sta[5]+sta[7]+sta[9]);
	if(!limit&&!zero&&fv[pos][num])
		return f[pos][num];
	int rig=limit?a[pos]:9,ans=0;
	for(int i=0;i<=rig;i++){
		bool is=(zero&&i==0);
		bool tvis=vis[i],tsta=sta[i];
		if(!is) vis[i]=1,sta[i]=!sta[i];
		ans+=dfs(pos-1,limit&&i==rig,is);
		vis[i]=tvis,sta[i]=tsta;
	}
	if(!limit&&!zero){
		f[pos][num]=ans,fv[pos][num]=1;
	}
	return ans;
}
int solve(int x){
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,1);
}
signed main(){
	memset(fv,0,sizeof fv);
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<solve(r)-solve(l-1)<<endl;
	}
	return 0;
}

运行消耗对比

从上到下分别是洛谷题解排名第一、朴素算法、空间优化、究极时空优化的代码的运行消耗,每个样例测试点均在\(5\)个以内。所以可以看出,朴素算法即可通过此题,而优化后的代码,无论在时间还是空间方面,都比题解优。

\[\textbf{——TO BE CONTINUED——} \]

标签:sta,int,30,pos,例题,优化,dp,数位
From: https://www.cnblogs.com/Sinktank/p/18132770

相关文章

  • 52 Things: Number 40: What is normally considered the difference between SPA and
    52Things:Number40:WhatisnormallyconsideredthedifferencebetweenSPAandDPA?52件事:第40件:通常认为SPA和DPA之间的区别是什么? Thisisthelatestinaseriesofblogpoststoaddressthelistof '52ThingsEveryPhDStudentShouldKnowToDoCryptogr......
  • DP Contest
    byTheBigYellowDuck涵盖了很多类型的dp问题,做一做还挺有趣的。做这个题单的时间跨度长达一年。为了完整性写了一些简单题。AFrog1设\(f(i)\)为跳到第\(i\)格上的最小花费,由于只能向后跳两格,容易写出转移方程\[f(i)=\min\{f(i-1)+|h_i-h_{i-1}|,f(i-2)+|h_i-h_{i-2......
  • DP 优化
    矩阵加速矩阵加速是数列递推中常用的技巧。当求解的项数巨大时,不妨考虑能否将状态压进矩阵里。通常,观察出来转移与某一维无关,而这一维恰好很巨大,也就是说同一种转移被做了很多遍的时候,不妨考虑有没有矩阵加速的可能。P8624垒骰子令\(f_{i,j}\)表示垒了\(i\)个骰子,朝上......
  • 区间dp 合并石子问题
    合并石子问题https://www.luogu.com.cn/problem/P1880[NOI1995]石子合并题目大致描述$N$堆石子摆成了一个圆,每相邻的两堆合成一堆,新的一堆的石子数为得分,求得分最小和最多$1\leqN\leq100$,$0\leqa_i\leq20$。解决思路:取每个区间的最大值1、获取数据,取得前缀和2、第一......
  • 关于期望 dp 的一点思考
    一、前言只是一些自己的理解,并不知正确与否。首先期望\(dp\)分为伪期望\(dp\)和真期望\(dp.\)二、伪期望\(dp\)对于伪期望\(dp\)来说,其在定义状态之后,一般可以认为状态之间的转移是线性的,即每一个\(dp\)状态转移到何处具有唯一对应性,只不过具体的实现上经过了概......
  • 线性DP解题
    DP是一种寻找最优解的算法。DP之所以效率高,是因为DP的算法中会记录重复状态,对于同一状态值进行一次求解。线性DP是DP中属于偏简单的一类。线性DP求解的关键则在于对第一个阶段或最后一个阶段进行分类讨论。线性DP的解题方法可以分为5个步骤:找出问题的阶段性,即问题分解的方法。......
  • dmdpc安装部署
    环境:OS:Centos7DM:DMV8达梦分布计算集群英文全称DMDistributedProcessingCluster,简称DMDPC.计划生成节点,英文全称为SQLProcessor,简称为SP;数据存储节点,英文全称为BackendProcessor,简称为BP;元数据服务器节点,英文全称为MetadataProcessor,简称为MP.一个最小的......
  • Deep Deterministic Policy Gradient(DDPG)算法讲解笔记
    DDPGDeepDeterministicPolicyGradient,基于actor-critic模型提出了一个有效的valuebased连续型空间的RL算法,引入了一些帮助训练稳定的技术。基础:DQN,Batchnormm,Discretize,微积分backgroundDQN改进的推广Policybased方法(TRPO)已经在actionspace取得突破传统disc......
  • ThreadPoolExecutor线程池解析
    ThreadPoolExecutor线程池解析一、ThreadPoolExecutor常见参数jdk中Executors提供了几种常用的线程池,底层都是ThreadPoolExecutor。publicThreadPoolExecutor(intcorePoolSize,//核心线程数intmaximumPoolSize,//最大线程数......
  • 动态规划dp
    动态规划(Dp)介绍动态规划(Dynamicprogramming)是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。动态规划问题的特点1.最优子结构性质:如果问题的最优......