数位dp模板+总结

18 篇文章 0 订阅
8 篇文章 0 订阅

HDU2089

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
inline int read(){
	char ch=' ';int f=1;int x=0;
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int dp[20][2];
int a[20];
int dfs(int pos,int pre,int state,bool limit)
{
	if(pos==0) return 1.;
	if(!limit&&dp[pos][state]!=-1) return dp[pos][state];
	int up=limit?a[pos]:9;
	int ans=0;
	for(int i=0;i<=up;i++)
	{
		if(i==4) continue;
		if(pre==6&&i==2) continue;
		ans+=dfs(pos-1,i,i==6,limit&&i==a[pos]);
	}
	if(!limit) dp[pos][state]=ans;
	return ans;
}
int solve(int x)
{
	int pos=0;
	while(x)
	{
		a[++pos]=x%10;
		x/=10;
	}
	return dfs(pos,0,0,1);
}
int main()
{
	int l,r;
	while(scanf("%d%d",&l,&r)!=EOF)
	{
		if(l==0&&r==0)
		{
			break;
		}
		memset(dp,-1,sizeof(dp));
		printf("%d\n",solve(r)-solve(l-1));
	}
	return 0;
}


HDU 3652

统计区间 [1,n] 中含有 ‘13’ 且模 13 为 0 的数字有多少个。
◦N<=10^9

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int f[10][2][2][20];
// f[pos][pre][have][k]
int a[10];
int p[12];
int dfs(int pos,bool pre,bool have,int k,bool limit)
{
	if(pos==-1&&k==0&&have) return 1;
	if(pos==-1) return 0;
	
	if(!limit&&f[pos][pre][have][k]!=-1) return f[pos][pre][have][k];
	
	int up=limit?a[pos]:9;int ans=0;
	for(int i=0;i<=up;i++)
	{
		int h=(k+i*p[pos])%13;
		ans+=dfs(pos-1,i==1,have||(pre&&i==3),h,limit&&i==a[pos]);
	}
	if(!limit) f[pos][pre][have][k]=ans;
	return ans;
}
int solve(int n)
{
	int pos=0;
	while(n)
	{
		a[pos++]=n%10;
		n=n/10;
	}
	return dfs(pos-1,0,0,0,true);
}
int main()
{
	int n;
	p[0]=1;
	for(int i=1;i<=10;i++) p[i]=p[i-1]*10;
	while(scanf("%d",&n)!=EOF)
	{
		memset(f,-1,sizeof(f));
		cout<<solve(n)<<endl;
	}
	return 0;
}

P2657 [SCOI2009]windy数

题目描述
windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,

在A和B之间,包括A和B,总共有多少个windy数?

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int a[12];
int f[12][12];
int dfs(int pos,int pre,bool limit,bool lead)
{
	if(pos==-1) return 1;
	if(!limit&&!lead&&f[pos][pre]!=-1) return f[pos][pre];
	
	int up=limit?a[pos]:9;int ans=0;
	if(!lead)
	for(int i=0;i<=up;i++)
	{
		if(abs(i-pre)<2) continue;
		ans+=dfs(pos-1,i,limit&&i==up,lead&&(i==0));
	}
	else
	for(int i=0;i<=up;i++)
	{
		ans+=dfs(pos-1,i,limit&&i==up,lead&&(i==0));
	}
	if(!limit&&!lead) f[pos][pre]=ans;
	return ans;
}
int solve(int n)
{
	int pos=0;
	while(n)
	{
		a[pos++]=n%10;
		n=n/10;
	}
	memset(f,-1,sizeof(f));
	return dfs(pos-1,0,true,true);
}
int main()
{
	int a,b;
	cin>>a>>b;
	cout<<solve(b)-solve(a-1)<<endl;
	return 0;
}

P4124 [CQOI2016]手机号码

题目描述
人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量。

工具需要检测的号码特征有两个:号码中要出现至少 33 个相邻的相同数字;号码中不能同时出现 88 和 44。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。

手机号码一定是 1111 位数,前不含前导的 00。工具接收两个数 LL 和 RR,自动统计出 [L,R][L,R] 区间内所有满足条件的号码数量。LL 和 RR 也是 1111 位的手机号码。

输入格式
输入文件内容只有一行,为空格分隔的 22 个正整数 L,RL,R。

输出格式
输出文件内容只有一行,为 11 个整数,表示满足条件的手机号数量。

输入输出样例
输入 #1
12121284000 12121285550
输出 #1
5
说明/提示
样例解释:满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550。

数据范围: 1 0 10 ≤ L ≤ R < 1 0 11 10^{10}\leq L\leq R<10^{11} 1010LR<1011

思路
  1. dp 要求记录详细,所以需要记录
    is8 -> 8是否出现
    is4->4是否出现
    same 前前和前是否相等
    pre 想一个具体是啥
    have 之前有没有三个相等
    limit
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ff f[pos][is8][is4][pre][same][have] 
#define ll long long
using namespace std;
ll a[15];
ll f[15][2][2][10][2][2];
//  pos  
ll dfs(int pos,bool limit,bool is8,bool is4,int pre,bool same,bool have)
{
	if(pos==-1&&have) return 1;
	if(pos==-1) return 0;
	if(!limit&&ff!=-1) return ff;
	
	int up=limit?a[pos]:9;ll ans=0;
	
	for(int i=0;i<=up;i++)
	{
		if(is8&&i==4) continue;
		if(is4&&i==8) continue;
		
		ans+=dfs(pos-1,limit&&i==up,is8||i==8,is4||i==4,
		i,i==pre,have||(same&&i==pre));
	}
	if(!limit) ff=ans;
	return ans;
}
ll solve(ll n)
{
	int pos=0;
	while(n)
	{
		a[pos++]=n%10;
		n=n/10;
	}
	memset(f,-1,sizeof(f));
	return dfs(pos-1,true,false,false,-1,false,false);
}
int main()
{
	ll n,m;
	cin>>n>>m;
	cout<<solve(m)-solve(n-1)<<endl;
	return 0;
}

P4317 花神的数论题

设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问
你派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。答案对一个质数取模。
◦对于 100% 的数据,N≤10^15

思路
  1. 数位dp需要一个限制条件,而此题从1到N枚举,一是没有限制条件,二是复杂度
  2. 所以我们枚举二进制中1的个数 i,看看1~N里有多少个符合的数a,对ans的贡献就是i^a(ksm)
  3. 注意a不能mod
  4. 这个题相对于裸的数位dp就是外面套了一层枚举,加一个转换
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int mod=10000007;
int a[70];
ll f[70][70];
ll ksm(ll x,ll y)
{
	int ret=1;
	while(y)
	{
		if(y&1) ret=(ll)ret*x%mod;
		x=(ll)x*x%mod;
		y=y>>1;
	}
	return ret;
}
ll dfs(int pos,int sum,bool limit)
{
	if(pos==0&&sum==0) return 1;
	if(pos==0) return 0;
	if(sum>pos) return 0;
	
	if(!limit&&f[pos][sum]!=-1) return f[pos][sum];
	
	int up=limit?a[pos]:1;ll ans=0;
	
	for(int i=0;i<=up;i++)
	{
		ans=ans+dfs(pos-1,sum-i,limit&&(i==up));
	}
	
	if(!limit) f[pos][sum]=ans;
	return ans;
}
ll solve(ll n)
{
	memset(f,-1,sizeof(f));
	int pos=0;
	while(n)
	{
		a[++pos]=n&1;
		n=n>>1;
	}
	ll ans=1,k;
	for(int i=1;i<=pos;i++)
	{
		k=dfs(pos,i,true);
		if(k)ans=(ll)ans*ksm(i,k)%mod;
	}
	return ans;
}
int main()
{
	ll n;cin>>n;
	cout<<solve(n)<<endl;
	return 0;
}

P2602 [ZJOI2010]数字计数

题目描述
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

输入格式
输入文件中仅包含一行两个整数a、b,含义如上所述。

输出格式
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。

输入输出样例
输入 #1
1 99
输出 #1
9 20 20 20 20 20 20 20 20 20
说明/提示
30%的数据中,a<=b<=10^6;

100%的数据中,a<=b<=10^12。

思路

  1. 我们枚举0~9,对每一个数值分别统计。
  2. 注意前导零的处理
int a[20];
ll dp[20][20];
ll dfs(int pos,ll sum,int limit,int lead,int k)
{
	if(pos==-1) return sum;
	if(!limit&&!lead&&dp[pos][sum]!=-1) return dp[pos][sum];
	int up=limit?a[pos]:9;
	ll ans=0;
	
	for(int i=0;i<=up;i++)
	{
		ans+=dfs(pos-1,sum+((!lead||i)&&(i==k)),limit&&(i==up),lead&&(i==0),k);
	}
	if(!limit&&!lead) dp[pos][sum]=ans;
	return ans;
}
ll solve(ll x,int k)
{
	int pos=0;
	while(x)
	{
		a[pos++]=x%10;
		x=x/10;
	}
	memset(dp,-1,sizeof(dp));
	return dfs(pos-1,0,1,1,k);
}
int main()
{
	ll l,r;
	l=read();r=read();
	for(int i=0;i<=9;i++)
	{
		cout<<solve(r,i)-solve(l-1,i)<<' ';
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值