BZOJ 4667 小y的密码

本文介绍了一道关于数位DP和DFS的应用题,通过详细解析算法思路和给出具体实现代码,帮助读者理解如何结合数位DP和DFS解决特定类型的数学问题。

Description

废话不多说,反正小y高考屠场啦!!
然而我们知道,高考报志愿系统是需要密码的,而小y根本没有在意自己的密码,为了挑战人生,他决定自己把密码找出来。
不知道为什么,小y认为自己的密码是一个大小不超过n的正整数,且满足一个奇怪的性质。
设这个正整数各个数位上的数字按数值大小从小到大依次为a_1,a_2,…,a_m,那么就有
其中k和Limi是给定的常数。现在给定n、k、Limi,求有多少种可能的密码。

Input

一行三个非负整数n、k、Limi,用一个空格隔开
n<=10^9,k<=10, Limi<=2*10^9

Output

一行一个整数表示符合条件的不同密码数量。

Sample Input

100000 0 5

Sample Output

99999

HINT

Source

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

数位DP+dfs+思路~

注意到答案只与0~9每个数出现的次数有关,所以我们dfs出所有满足条件的数的组合,再利用数位DP和组合数计数来计算答案即可。

数位DP实在是太太太太太神奇辣!最好看一看代码!


#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define ll long long

int n,k,lim,a[12],c[12],tot,ans,inc[12],num[12];

bool che()
{
	if(!tot || !c[tot]) return 0;
	ll now=0,kkz=c[(tot+1)/2],tmp;
	for(int i=1;i<=tot;i++)
	{
		tmp=1;
		for(int j=1;j<=k;j++) tmp*=(ll)c[i]-kkz;
		now+=tmp;
	}
	return now<=lim;
}

int calcas(int u)
{
	int now=inc[u];
	for(int i=0;i<=9;i++) now/=inc[num[i]];
	return now;
}

void cal()
{
	memset(num,0,sizeof(num));
	for(int i=1;i<=tot;i++) num[c[i]]++;
	if(tot<a[0])
	{
		ans+=calcas(tot);
		if(num[0])
		{
			num[0]--;ans-=calcas(tot-1);
		}
		return;
	}
	int flag=1,tmp=tot;
	for(int i=tot;i;i--)
	{
		for(int j=(i==tot);j<a[i];j++)
		  if(num[j])
		  {
			num[j]--;tmp--;
			ans+=calcas(tmp);
			num[j]++;tmp++;
		  }
		if(!num[a[i]])
		{
			flag=0;break;
		}
		num[a[i]]--;tmp--;
	}
	ans+=flag;
}

void dfs(int u)
{
	if(che()) cal();
	if(tot==a[0]) return;
	tot++;
	for(int i=u;i<=9;i++) c[tot]=i,dfs(i);
	tot--;
}

int main()
{
	scanf("%d%d%d",&n,&k,&lim);
	while(n) a[++a[0]]=n%10,n/=10;inc[0]=1;
	for(int i=1;i<=a[0];i++) inc[i]=inc[i-1]*i;
	dfs(0);
	printf("%d\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值