数位 dp

数位DP

  • 前导零

  • 是否压位

例题:

不要62

题面:

求 a 到 b 中不含连续的 62 并且不含 4 的数。

做法:

记 \(g(x)\) 为 0 到 x 之间的 windy 数。

求 \(g(b)-g(a-1)\)

深搜,记长度 x ,上一个数 last,是否压界 k。

枚举此位放几,排除不合法情况,累计答案。

#include<bits/stdc++.h>
using namespace std;
int s[11];
int w;
int dfs(int n,int k,int last)
{
	if(n==0) return 1;
	int num=0;
	int up=k?s[n]:9;
	for(int i=0;i<=up;i++)
	{
		if(i==4) continue;
		if(last==6&&i==2) continue;
		num+=dfs(n-1,k&(i==s[n]),i);
	}
	return num;
}
int js(int n)
{
	if(n<10){
		if(n>=4) return n;
		if(n<4) return n+1;
	}
	w=0;
	while(n)
	{
		w++;
		s[w]=n%10;
		n=n/10;
	}
	return dfs(w,1,0);
}
int main()
{
	int a,b;
	while(scanf("%d%d",&a,&b)){
		if(a==0&&b==0) return 0;
		printf("%d\n",js(b)-js(a-1));
	}
}

P2657 [SCOI2009] windy 数

题面:

不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 a 和 b 之间,包括 a 和 b ,总共有多少个 windy 数。

做法:

记 \(g(x)\) 为 0 到 x 之间的 windy 数。

求 \(g(b)-g(a-1)\)

设 \(f[x][i]\) 表示长度为 \(x\) 最高位为 \(i\) 的 windy 数的个数

\(0<=i<=9\)

\(0<=x<=10\)

\(f[x][i]=f[x-1][j1]+f[x-1][j2]...\)

\(abs(i-j)>=2\)

记 \(ff[x]\) 为长度小于等于 \(x\) 的 windy 数的个数。

\(ff[x]=ff[x-1]+f[x][0]+f[x][2]+...f[x][9]\)

深搜,记搜到第几位数 x,是否压界 k,上一位数 last。

如果未压界 则可直接返回。

如果压界 根据 last 枚举此位可放的数,搜索并累加答案。

code:

#include<bits/stdc++.h>
using namespace std;
int f[11][11];
int s[11];
int w;
int dfs(int n,int k,int last)
{
	if(n==0) return 1;
	if(k==0)return f[n+1][last==-2?10:last];
	int num=0;
	if(last-2>=0)
	{
		for(int i=0;i<=last-2;i++){
			if(i>=s[n]) break;
			num+=dfs(n-1,0,i);
		}
	}
	if(last+2<=9)
	{
		for(int i=last+2;i<=9;i++){
			if(i>=s[n]) break;
			num+=dfs(n-1,0,i==0?(last==-2?-2:0):i);
		}
	}
	if(abs(s[n]-last)>=2)
	num+=dfs(n-1,1,s[n]);
	return num;
}
int js(int n)
{
	if(n<10) return n+1;
	w=0;
	while(n)
	{
		w++;
		s[w]=n%10;
		n=n/10;
	}
	return dfs(w,1,-2);
}
int main()
{
	for(int i=0;i<=10;i++)
	f[1][i]=1;
	for(int i=2;i<=10;i++)
	{
		for(int j=0;j<=9;j++)
		{
			for(int k=0;k<=9;k++)
			{
				if(abs(k-j)>=2)
				f[i][j]+=f[i-1][k];
			}
		}
		for(int k=1;k<=10;k++)
		f[i][10]+=f[i-1][k];
	}
	int a,b;
	cin>>a>>b;
	cout<<js(b)-js(a-1);
}

P4124 [CQOI2016]手机号码

题面:

号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8 和 4。

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

做法:

换汤不换药。

记录剩几位数,上一个数和上上一个数是几,是否有连续的三个数,是否有 4 和 8,是否压界,深搜过程中筛选即可。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int t[15];
ll r,l,f[12][12][12][2][2][2][2];
ll dfs(int p,int a,int b,bool t3,bool k,bool t4,bool t8)
{
	if(t4&&t8) return 0;
	if(p==0) return t3;
	if(f[p][a][b][t3][k][t4][t8]!=-1) return f[p][a][b][t3][k][t4][t8];
	ll num=0;
	int up=k?t[p]:9;
	for(int i=0;i<=up;i++)
	num+=dfs(p-1,b,i,t3||(a==b&&b==i),k&&(i==up),t4||(i==4),t8||(i==8));
	return f[p][a][b][t3][k][t4][t8]=num;
}
ll work(ll n)
{
	if(n<1e10) return 0;
	memset(f,-1,sizeof(f));
	int p=0;
	while(n){
		t[++p]=n%10;
		n/=10;
	}
	ll ans=0;
	for(int i=1;i<=t[11];i++)
	ans+=dfs(10,-1,i,0,i==t[11],i==4,i==8);
	return ans;
}
int main()
{
	cin>>l>>r;
	cout<<work(r)-work(l-1);
 } 

P4317 花神的数论题

题面:

设 \(sum_i\) 为 i 二进制下一的个数。
给出一个正整数 N ,花神要问你\(\prod_{i=1}^nsum_i\)

做法:

求出有多少个数有 \(i\) 个一,最后快速幂计算。

总左往右 dp ,找到第一个 1 时将其设为零,后面不压位可 dp,如果遇到 1 (设为第 x 个一),那么可将前 x 个变为一,此为变为 0, 为一种新方案,供后续 dp,计入前面原数为一的为一,此位为零的方案。最后记上全为一的方案。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
ll n;
int a[65];
ll mod=10000007;
int p;
ll qm(ll x,int p)
{
	if(p==0) return 1;
	ll tmp=qm(x,p>>1);
	if(p&1) return tmp*tmp%mod*x%mod;
	else return tmp*tmp%mod;
}
signed main()
{
	cin>>n;
	for(int j=49;~j;--j){
		for(int i=49;i;--i)
			a[i]+=a[i-1];
		if(n>>j&1) ++a[p++];
	} 
	a[p]++;
	ll ans=1;
	for(int i=1;i<=55;i++)
	ans=(ans*qm(i,a[i]))%mod;
	cout<<ans;
 } 
上一篇:.运行npm install 时,卡在sill idealTree buildDeps没有反应


下一篇:npm 和 yarn 更改为淘宝镜像