显然可以把答案转化为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