BZOJ2425:[HAOI2010]计数(数位DP)

Description

你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数。比如说给定{1,2},那么可以生成数字12,21,102,120,201,210,1002,1020,等等。

现在给定一个数,问在这个数之前有多少个数。(注意这个数不会有前导0).

Input

只有1行,为1个整数n.

Output

只有整数,表示N之前出现的数的个数。

Sample Input

1020

Sample Output

7

HINT

n的长度不超过50,答案不超过263-1.

Solution

感觉我的数位DP还不是很稳啊……
全靠面向WA和面向样例来编程(逃
首先这个题并不是很难,因为很容易想到:
当某一位没有限制的时候,这一位以及后面的位数就可以用全排列来求数的个数了
求全排列时要去重
只不过数据太大所以求全排列的时候要分解质因数……否则只有60分
啊啊啊好多细节烦死了

Code

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
LL num[],a[],cnt,pos,num_sum,Keg[];
LL used[],prime[]={,,,,,,,,,,,,,,,,};
char n[]; void divide(LL x,LL k)//将x分解质因数加入桶内
{
for (LL i=;i<=;++i)
while (x%prime[i]== && x)
{
Keg[i]+=k;
x/=prime[i];
}
} LL check(LL x)
{
LL sum=;
memset(Keg,,sizeof(Keg));//桶清空
for (int i=;i<=x;++i) divide(i,);//求全排列
for (LL i=;i<=;++i)//若一个数字出现多次就肯定会重复,重复个数为num[i]!
{
for (int j=;j<=num[i];++j) divide(j,-);
x-=num[i];
}
if (x<) return ;//这里的意思是,全排列的位数摆不开那些没有使用的非0数字
for (int i=;i<=x;++i) divide(i,-);//别忘了0也要去重
for (int i=;i<=;++i)
for (int j=;j<=Keg[i];++j)
sum*=prime[i];
return sum;
} LL Dfs(LL pos,LL limit,LL used)
{
if (pos==) return used==num_sum;//必须要所有非0数字用光才能返回1
if (!limit)
return check(pos);//没有限制,直接求后面的全排列 LL ans=,up=limit?a[pos]:;
for (LL i=;i<=up;++i)
{
if (num[i]<= && i!=) continue;
num[i]--;
ans+=Dfs(pos-,limit && i==a[pos],used+(i!=));
num[i]++;
}
return ans;
} int main()
{
scanf("%s",n);
for (int i=strlen(n)-;i>=;--i)
{
if (n[i]!='')
num[n[i]-'']++,num_sum++;
a[++pos]=n[i]-'';
}
printf("%lld",Dfs(pos,true,)-);
}
上一篇:centos7.4 update git


下一篇:bzoj千题计划178:bzoj2425: [HAOI2010]计数