数位dp训练笔记

news2025/7/9 6:01:53

依稀还记得去年寒假的时候对数位dp的恐惧达到了顶峰,打死也不想做一题,也是怎么学都学不会,甚至板子也只是真的去网上copy了一份,自己也都不理解。(羞愧)

这个状态持续了一年多(羞愧羞愧),于是上周痛改前非,逼着自己重学数位dp,然后惊喜的发现,里面也没什么东西

刷了一定题目之后会发现还是有一定的套路可循的,在dp系列里应该也不能算很难的那一种(所以我为什么现在才开始学...)

1 前缀和思想处理区间问题,然后差分

2 按位枚举处理

3 合并相同状态做dp

时间复杂度的话一般就是状态数之和,毕竟相同状态会合并

没了

[AHOI2009]同类分布

大意:

给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。

a,b<=1e18

思路:

考虑dp状态dp[i][j][k]表示枚举到前i位,前i位数字的和是j,前i位组成的数字是k

这是一个比较niave的想法,但是数据范围不支持我们这样处理第三维。

考虑到最后只要求整除,所以可以考虑用k%j来代替k

但是这样还涉及到一个问题,就是如果j是不断变化的,就很难实现状态转移

所以我们可以在外层枚举j,然后dfs的时候就保持j不变

就好了

由于这里是考虑每一位数字的和,所以我们不用考虑前导0的问题

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1e5+10;
ll mod;
ll n,m;
ll a[20];
ll cnt=0;
ll dp[20][200][200];
ll dfs(ll x,ll sum,ll rel,ll op)
{
	if(x==0) return sum==0&&rel==0;
	if(!op&&dp[x][rel][sum]!=-1) return dp[x][rel][sum]; 
	ll lim=op?a[x]:9;
	ll tot=0;
	for(int i=0;i<=lim&&i<=sum;++i)
	{	
		tot+=dfs(x-1,sum-i,(rel*10%mod+i)%mod,op&&i==lim);	
	}
	if(!op) dp[x][rel][sum]=tot;
	return tot;
}
ll f(ll x)
{
	cnt=0;
	while(x)
	{
		a[++cnt]=x%10;
		x/=10;
	}
	ll det=0;
	for(int i=1;i<=9*cnt;++i)
	{
		mod=i;
		memset(dp,-1,sizeof dp);
		det+=dfs(cnt,i,0,1);
	}
	return det;
}
void solve()
{
	
	cin>>n>>m;
	cout<<f(m)-f(n-1)<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
//	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

虽然是紫题,但是理解了套路就很水

Classy Numbers

大意:

给出两个数a,b,求出[a,b]中各位数字中非0数不大于3的数字个数。

a,b<=1e18

思路:
板子题

这里显然需要考虑前导0

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1e5+10;
ll b,l,r;
int a[20];
ll cnt=0;
ll dp[20][200];
ll dfs(ll x,int sum,ll op)
{
	if(x==0) return sum<=3;
	if(!op&&dp[x][sum]!=-1) return dp[x][sum];
	int lim=op?a[x]:9;
	ll tot=0;
	for(int i=0;i<=lim;++i)
	{
		tot+=dfs(x-1,sum+(i!=0),op&&i==lim);
	}
	if(!op) dp[x][sum]=tot;
	return tot;
}
ll f(ll x)
{
	cnt=0;
	while(x)
	{
		a[++cnt]=x%10;
		x/=10;
	}
	return dfs(cnt,0,1);
}
void solve()
{
	cin>>l>>r;
	cout<<f(r)-f(l-1)<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	memset(dp,-1,sizeof dp);
	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

Segment Sum

大意:

给定K,L,R,求L~R之间最多不包含超过K种数码的数的和。

K<=10,L,R<=1e18

思路:
如果只是求满足条件的数字的个数的话就是上面提到的板子题了

这里要求和,我们同样可以考虑对相同状态进行合并求和

f[i][j]表示当前枚举到第i位,出现过的数码种类的状态为j,也就是我们要求的答案数组

g[i][j]表示当前枚举到第i位,出出现过的数码种类的状态为j的合法数字个数,也就是板子

考虑如何用g来推f

这里j可以10位二进制状压

我们每往下走一位,如果我们枚举第i位上填的数字是t,用j'表示下一位的数码种类数

g[i][j]=\sum g[i-1][j']

f[i][j]=\sum 10^i*t*g[i-1][j']+f[i-1][j']

稍微意会一下应该就能懂了

f是数字的和,g是合法数字的个数

有了这个之后,我们就直接推就可以了。然后因为需要下一个状态的f和g,所以我们dfs要返回两个值

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<ll,ll>
#define mk make_pair
#define endl '\n'
const ll N=1e5+10;
const ll mod=998244353;
ll n,m,k;
ll a[20];
ll cnt=0;
pii dp[40][1030];
ll p[40];
void init()
{
	p[0]=1;
	for(int i=1;i<=20;++i) p[i]=p[i-1]*10ll%mod;
}
bool check(ll x)
{
	ll cn=0;
	while(x)
	{
		cn+=(x%2);
		x/=2;
	}
	return cn<=k;
}
pii dfs(ll x,ll sum,ll head,ll op)
{
	if(x==0) return mk(0,1);
	if(!op&&!head&&dp[x][sum]!=mk(-1ll,-1ll)) return dp[x][sum];
	ll lim=op?a[x]:9;
	ll s1=0,s2=0;
	for(ll i=0;i<=lim;++i)
	{
		//f是数字的和,g是合法数字的个数
		pii gt=mk(0,0);
		if(head&&i==0) gt=dfs(x-1,0,1,op&&i==lim);
		else if(check(sum|(1<<i))) gt=dfs(x-1,sum|(1<<i),0,op&&i==lim);
		s1=(((s1+i*p[x-1]%mod*gt.second%mod)%mod)+gt.first)%mod;	
		s2=(s2+gt.second)%mod;
	} 
	if(!op&&!head) dp[x][sum]=mk(s1,s2);
	return mk(s1,s2);
}
ll f(ll x)
{
	cnt=0;
	while(x)
	{
		a[++cnt]=x%10;
		x/=10;
	}
	return dfs(cnt,0,1,1).first;
}
void solve()
{
	init();
	for(int i=0;i<=20;++i)
	{
		for(int j=0;j<=1025;++j)
		{
			dp[i][j]=mk(-1ll,-1ll);
		}
	}
	cin>>n>>m>>k;
	cout<<((f(m)-f(n-1))%mod+mod)%mod<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}

 E. Salazar Slytherin's Locket

大意:
求l...r之间转成b进制后,每一位都是偶数的数的个数

思路:
不再是10进制,只要在预处理每一位的时候换个底数就好了

注意一下前导0

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1e5+10;
ll b,l,r;
int a[70];
ll cnt=0;
ll dp[70][1026][12];
ll dfs(ll x,int sta,ll op,ll head)
{
	if(x==0) return sta==0;
	if(!op&&!head&&dp[x][sta][b]!=-1) return dp[x][sta][b];
	int lim=op?a[x]:b-1;
	ll tot=0;
	for(int i=0;i<=lim;++i)
	{
		if(head&&i==0) tot+=dfs(x-1,0,op&&i==lim,1);
		else tot+=dfs(x-1,sta^(1<<i),op&&i==lim,0);
	}
	if(!op&&!head) dp[x][sta][b]=tot;
	return tot;
}
ll f(ll x)
{
	cnt=0;
	while(x)
	{
		a[++cnt]=x%b;
		x/=b;
	}
	return dfs(cnt,0,1,1);
}
void solve()
{
	//now++;
	cin>>b>>l>>r;
	cout<<f(r)-f(l-1)<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	memset(dp,-1,sizeof dp);
	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

花神的数论题

大意:
sum(i)表示i的二进制表示中1的个数,求\prod_{i=1}^{n} sum(i)

思路:
考虑枚举贡献,枚举二进制1的个数为j,假设有k个这样的数字,答案就是\prod j^{k}

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e7+7;
const ll N=70;
ll cnt[N];
ll n;
ll p,ans=1;
ll dp[N][N];//dp[i][j],当前枚举到第i位,前面的数中
//有j个1的情况下最终满足sum值等于p的数字的个数 
ll ksm(ll x,ll y)
{
	ll ans=1;
	while(y)
	{
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}

ll dfs(ll pos,bool limit,ll sum)
{
	if(pos==0) return (sum==p);//边界条件
	if(!limit&&~dp[pos][sum]) return dp[pos][sum];
	ll up=limit?cnt[pos]:1;
	ll cn=0;
	for(int i=0;i<=up;++i)
	{ 
		if(sum+(i==1)>p) continue;
		cn+=dfs(pos-1,limit&&(i==up),sum+(i==1));
	} 
	if(!limit) dp[pos][sum]=cn;
	return cn;
	 
}
void solve()
{
	ll d=0;
	while(n)
	{
		cnt[++d]=n%2;
		n/=2;
	}
	for(int i=1;i<=d;++i)
	{
		memset(dp,-1,sizeof dp);
		p=i;
		ans=ans*ksm(i,dfs(d,1,0))%mod;
	}
	cout<<ans<<endl;
}
int main()
{
	cin>>n;
	solve();
	return 0;
}

未完待续~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/618908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows操作/文件/设置/DOS 记录

目录 1.系统操作 1.环境变量 2.文件夹操作 1.显示隐藏文件夹 3.DOS窗口 1.DOS窗口中docker切换管理员root /]#身份: docker run -it centos​编辑 4.文件操作 1.图片分辨率无损修改尺寸&#xff08;例1280x800&#xff09; 2.图片修改png/jpg文件后缀类型 1.系统操作 1.…

Python 语句

文章目录 一、条件语句1、顺序语句2、条件语句3、缩进和代码块4、条件语句练习5、空语句 二、循环语句1、while2、for3、break和continue 一、条件语句 1、顺序语句 从上到下依次执行 2、条件语句 Python中使用if else关键字表示条件语句. ①if if expression:do_somethi…

PCB板的Mark点设计对SMT重要性

Mark点也称光学点、基准点&#xff0c;是电路板元器件组装中&#xff0c;PCBA应用于自动贴片机上的位置识别点。 Mark点的选用&#xff0c;直接影响到自动贴片机的贴片效率&#xff0c;因此在设计时&#xff0c;需要设计好Mark点以及其在板内的位置。 Mark点的设计 1、布局位…

String s = new String(“xyz“) 创建了几个对象?

这个问题相信每个学习 java 的同学都不陌生&#xff0c;作为一个经典的面试题&#xff0c;到现在工作这么多年了我真是认为挺操蛋的一个问题&#xff0c;在网上到现在你仍然可以看见很多讨论这个问题的人&#xff0c;其中不乏工作很多年的人都有争论&#xff0c;我认为还是有必…

GreatSQL删除分区慢的跟踪

GreatSQL删除分区慢的跟踪 背景 某业务系统&#xff0c;每天凌晨会删除分区表的一个分区(按天分区)&#xff0c;耗时较久&#xff0c;从最开始的30秒&#xff0c;慢慢变为1分钟&#xff0c;影响到交易业务的正常进行。 在测试环境进行了模拟&#xff0c;复现了删除分区慢的情…

市场火爆!三大发展优势加速汽车零部件行业布局

当前&#xff0c;中国新能源汽车自主品牌崛起&#xff0c;为汽车零部件发展带来新机遇&#xff1b;有别于传统汽车零部件市场&#xff0c;新能源领域&#xff0c;主机厂标准提升&#xff0c;对数字化要求逐渐提高&#xff0c;汽车零部件企业的智能制造异常重要&#xff0c;企业…

二分类结局变量Logistic回归临床模型预测(二)——3. 单因素多因素logistic回归分析及三线表(三)

本节讲的是二分类结局变量的临床模型预测,与之前讲的Cox回归不同,https://lijingxian19961016.blog.csdn.net/article/details/124088364https://lijingxian19961016.blog.csdn.net/article/details/124088364https://lijingxian19961016.blog.csdn.net/article/details/1300…

1929-2022年全球站点的逐月最低气温(Shp\Excel\12000个站点)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2022年全球气象站…

Qt学习之旅 -信号与槽

文章目录 点击关闭窗口自定义信号和槽自定义信号和槽解决重载问题信号和连接信号断开连接Qt4版本信号槽连接Lambda表达式 点击关闭窗口 connect(信号发送者&#xff0c;发送的具体信号,信号接收者&#xff0c;型号的处理(槽slot));这里自定义的MyPushButton与QPushButton别无二…

【NLP】KMP匹配算法

一、说明 KMP算法。也称为Knuth-Morris-Pratt字符串查找算法可在一个字符串S内查找一个词W的出现位置。一个词在不匹配时本身就包含足够的信息来确定下一个匹配可能的开始位置&#xff0c;此算法利用这一特性以避免重新检查先前配对的字符。将时间复杂度从O(M*N)降为O(N). 这个…

C++ Primer Plus 第三章习题

目录 复习题 1. 为什么C有多种整型&#xff1f; 2. 声明与下述描述相符的变量&#xff1f; 3. C 提供了什么措施来防止超出整型的范围&#xff1f; 4. 33L和33之间有什么区别&#xff1f; 5. 下面两条C语句是否等价&#xff1f; 6. 如何使用C来找出编码88表示的字符&…

又一个生物标志物ADMA被发现!可为OA治疗提供新方向!

文章标题&#xff1a;Metabolite asymmetric dimethylarginine (ADMA) functions as a destabilization enhancer of SOX9 mediated by DDAH1 in osteoarthriti 发表期刊&#xff1a;Science Advances 影响因子&#xff1a;14.95 作者单位&#xff1a;浙江大学医学院附属邵逸…

EasyUi03

1.无限极分类. 1.1无限极分类介绍. 1.1.1何为无限极分类. 无限极分类简单点说就是一个类别能够分多个子类&#xff0c;而后一个子类又能够分多个子类&#xff0c;就这样无限分下去&#xff0c;就好象 windows能够新建一个文件夹&#xff0c;而后在这个文件夹里又能够建一些文…

《嵌入式系统》知识总结12:SysTick定时器

SysTick定时器 系统时钟&#xff08;SysTick&#xff09; Corte-M3在内核中包含的简单定时器 • 该定时器的时钟源可以来自CM3内部时钟&#xff08;FCLK&#xff09;&#xff0c;或CM3外部时钟&#xff08;STCLK&#xff09; • 在STM32微控制器中&#xff0c;SysTick的时钟源可…

平板触控笔哪款好用?电容笔牌子排行

现如今&#xff0c;电容笔越来越受欢迎&#xff0c;不少人在记笔记、学画画甚至是玩游戏的时候都会使用它。最近看到很多人问&#xff0c;iPad电容笔哪款好用&#xff1f;针对这个问题&#xff0c;我来给大家推荐四款公认好用的平替电容笔&#xff0c;一起来看看吧。 一、主动…

实验篇(7.2) 08. 通过安全隧道访问内网服务器 (FortiClient-IPsec) ❀ 远程访问

【简介】通过对SSL VPN与IPsec VPN的对比&#xff0c;我们知道SSL VPN是基于应用层的VPN&#xff0c;而IPsec VPN是基于网络层的VPN&#xff0c;IPsec VPN对所有的IP应用均透明。我们看看怎么用FortiClient实现IPsec VPN远程访问。 实验要求与环境 OldMei集团深圳总部部署了一台…

眼底图片解读(对比图!!!)

目录 1. 前言 2.常见眼底解析 (1) 黄斑变性 (2) 糖尿病视网膜病变 (3) 青光眼 (4) 视网膜血管阻塞 (5)视网膜裂孔和脱离 1. 前言 眼底图像是通过眼底摄影等技术获取的眼底部位的影像&#xff0c;可以提供关于眼睛健康和疾病的重要信息。以下是眼底图像中常见的信息和相关…

只见新人笑,不见旧人哭 ChatGPT淘汰了多少产品?快来了解!

ChatGPT作为目前世界上最先进的人工智能聊天工具&#xff0c;其GPT模型就是一种自然语言处理&#xff08;NLP&#xff09;模型&#xff0c;使用多层变换器&#xff08;Transformer&#xff09;来预测下一个单词的概率分布&#xff0c;通过训练在大型文本语料库上学习到的语言模…

chatgpt赋能python:Python自动运行教程:让你的工作更智能化

Python自动运行教程&#xff1a;让你的工作更智能化 Python是一种高级、解释型、面向对象的编程语言&#xff0c;被广泛应用于数据分析、机器学习和自动化任务等领域。除此之外&#xff0c;Python还能够实现自动化运行&#xff0c;让用户无需手动干预&#xff0c;从而减轻工作…

Think系列产品进入BIOS的操作方法

Think系列产品进入BIOS的操作方法&#xff1a; 适用范围&#xff1a;ThinkPad全系列笔记本ThinkCentre全系列一体机ThinkStation全系列工作站 温馨提示&#xff1a;如果您用的是Win8/8.1系统&#xff0c;小乐强烈建议您在系统下执行“重启”后的开机界面(切记&#xff1a;不是从…