BZOJ 1833 【ZJOI2010】 数字计数

题目链接:数字计数

  没啥好说的,裸裸的数位(dp)。

  先枚举当前是算数字(x)出现的次数,设(f_{i,j})表示从高位往低位(dp),(dp)完了前(i)位之后(x)出现了(j)次的方案数。然后再加一维,表示当前这一位能否自由选数(也就是说之前是否是一路选最大值过来的)。转移分情况讨论一下就好了。

  注意这种写法还有一点情况,就是算(0)出现的次数时需要减去区间内前导(0)的个数。

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)

using namespace std;
typedef long long llg;

llg l,r,f[21][21][2],mi[21];
int a[21],b[21],l1,l2;

void divide(llg x,int *s,int &len){
	llg y=x; while(y) len++,y/=10;
	for(int i=len;i;i--) s[i]=x%10,x/=10;
}

llg work(int *s,int n,int x){
	f[0][0][1]=1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++){
			f[i][j][0]=f[i-1][j][0]*9;
			if(j) f[i][j][0]+=f[i-1][j-1][0];
			if(s[i]>x){
				f[i][j][0]+=f[i-1][j][1]*(s[i]-1);
				if(j) f[i][j][0]+=f[i-1][j-1][1];
			}
			else f[i][j][0]+=f[i-1][j][1]*s[i];
			if(s[i]==x){
				if(j) f[i][j][1]=f[i-1][j-1][1];
				else f[i][j][1]=0;
			}
			else f[i][j][1]=f[i-1][j][1];
		}
	llg now=0;
	for(int i=1;i<=n;i++) now+=i*(f[n][i][0]+f[n][i][1]);
	if(!x){
		mi[0]=1; now-=n;
		for(int i=1;i<n;i++) mi[i]=mi[i-1]*10;
		for(int i=1;i<n;i++) now-=(mi[i]-mi[i-1])*(n-i);
	}
	return now;
}

int main(){
	File("a");
	scanf("%lld %lld",&l,&r); l--;
	divide(l,a,l1); divide(r,b,l2);
	for(int i=0;i<=9;i++){
		if(i) printf(" ");
		printf("%lld",work(b,l2,i)-work(a,l1,i));
	}
	return 0;
}
原文地址:https://www.cnblogs.com/lcf-2000/p/6395245.html