bzoj4521 手机号码 数位dp

       显然可以把答案转化为ans(r+1)-ans(l)(注意我用的开区间),那么考虑ans(n)。

       令f[i][j][x][y]表示当前到第i位,和数n的前i为关系为j(为0表示<,1表示随意),x=1表示有4,x=2表示有8,的情况下的答案;令f[i][j][x][y][k][p](i,j,x,y同上)表示最后一位为k,连续的状态为p(p=0表示没有连续;p=1表示有两个连续)的答案,然后枚举当前位的数字,上一位的数字和x,y然后转移一下就好了。

       不需要注意前导0的问题,因为前导0会被抵消;但如果r=10^12-1,那么+1之后位数不同导致不能抵消则需要特判。

AC代码如下:

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

ll f[15][2][2][2],g[15][2][2][2][10][2]; int a[15];
ll solve(ll n){
	int i,j,k,l,x,y,t,u,v,len=0;
	for (; n; n/=10) a[++len]=n%10;
	memset(f,0,sizeof(f)); memset(g,0,sizeof(g));
	for (j=0; j<2; j++)
		for (k=0; k<max(j*10,a[1]); k++)
			g[1][j][k==4][k==8][k][0]=1;
	for (i=2; i<=len; i++)
		for (j=0; j<2; j++)
			for (x=0; x<2; x++) for (y=0; y<2; y++)
				for (k=0; k<=max(j*9,a[i]); k++) if ((x || k!=4) && (y || k!=8)){
					t=j|(k<a[i]);
					for (u=x&(k!=4); u<=x; u++) for (v=y&(k!=8); v<=y; v++){
						f[i][j][x][y]+=f[i-1][t][u][v]+g[i-1][t][u][v][k][1];
						g[i][j][x][y][k][1]+=g[i-1][t][u][v][k][0];
						for (l=0; l<=max(t*9,a[i-1]); l++)
							if ((u || l!=4) && (v || l!=8) && l!=k)
								g[i][j][x][y][k][0]+=g[i-1][t][u][v][l][0]+g[i-1][t][u][v][l][1];
					}
				}
	return f[len][0][0][1]+f[len][0][1][0]+f[len][0][0][0];
}
int main(){
	ll ans=0,l,r; scanf("%lld%lld",&l,&r);
	if (r+1==100000000000LL){ ans++; r--; }
	ans+=solve(r+1)-solve(l); printf("%lld\n",ans);
	return 0;
}

by lych

2016.4.22

你可能感兴趣的:(动态规划,数位dp)