别的我不说了,良心题解先吹一波好吧。
最重要的使这一段
假如第一位4,第二位7,三四位就可以随便取了。设这种状态为(2,2),意思为前两位已经匹配上了4750的第二位。则用dp[2][2]表示(dp[100010][5],一共5*1e5种状态)。
那么(2,2)就有10种选择(0~9):
·会转移1次到(3,3)(选5);
·1次(3,1)(选4);
·8次(3,0)(选其他)
在一番推导后,能得到状态转移方程如下:
for (int i = 0; i <= upp; i++) //从0到当位可以取到的最大值
{
if (key[now + 1] == i) //一是匹配到下一位
ret += dfs(pos + 1, now + 1, lim && upp==i, s, pre, num, len);
else if (key[1] == i && now != 0) //一是匹配回第一位,但是注意不能和第一种情况重了,会增加答案数量
ret += dfs(pos + 1, 1, lim && upp==i, s, pre, num, len);
else //还有瞎几把配,配到下一位一位都配不起,更新
ret += dfs(pos + 1, 0, lim && upp==i, s, pre, num, len);
ret %= zh;
}
整个dfs函数思路如下:
void dfs(){
如果当前的位置超过了总长度,就返回,如果当前配到了足够的4位就返回1,不然就0;
如果这个点之前已经搜过(且此时没有取值限制),那么之前存下来的值就可以直接调用,(这是个记忆化搜索)
如果没搜过的话,看当前是否能凑足4位,凑足就可以在本轮递归中更新个ret。
不能凑足的话,循环下一位去匹配,就是如上所示的状态转移
最后有一个更新记忆化的操作,然后return
}
我的写法:
int dfs(int pos, int now, int lim, char *s, int pre[], int num[],int len)
//当前位置,当前匹配的多少位置,对于取位的限制,当前字符串,然后是一些提前预处理过的数组,最后是当前串的长度
{
if (pos > len)
return now == 4; //如果当前的位置超过了总长度,就返回,如果当前配到了足够的4位就返回1,不然就0;
if (!lim && dp[pos][now] != -1)
return dp[pos][now]; //如果这个点之前已经搜过(且此时没有取值限制),那么之前存下来的值就可以直接调用
int ret = now == 4; //如果没搜过的话,看当前是否能凑足4位,凑足就可以在本轮递归中更新个ret
int now1 = now;
now = now == 4 ? 0 : now;
if(!lim) //注意因为限制的不同,ret的处理是乘pre数组还是num数组是有区别的,后文会讲
ret = ret * pre[pos] % zh;
else
ret = ret * (num[pos] + 1) % zh;
int upp = lim ? s[pos]-'0' : 9;
for (int i = 0; i <= upp; i++) //不能凑足的话,循环下一位去匹配,就是如上所示的状态转移
{
if (key[now + 1] == i)
ret += dfs(pos + 1, now + 1, lim && upp==i, s, pre, num, len);
else if (key[1] == i && now != 0)
ret += dfs(pos + 1, 1, lim && upp==i, s, pre, num, len);
else
ret += dfs(pos + 1, 0, lim && upp==i, s, pre, num, len);
ret %= zh;
}
if (!lim)
dp[pos][now1] = ret % zh; //最后有一个更新记忆化的操作,然后return
return ret % zh;
}
if(!lim)
ret = ret * pre[pos] % zh;
else
ret = ret * (num[pos] + 1) % zh;
因为限制的不同,在取到4位的情况下,
如果不被限制,那么后面所有位数取0到9都行,那么就是一个10的次方倍数的个数可以被累加进ret,这就是当前位置的pre数组存储的数字。
如果被限制了,则只能取到当前位后面的数字+1个,比如题解中475027,已经有了4750,那么可以选0到27有28种选择,每一位都提前计算并储存在num里。
这里重点讲一下这个限制 limit,算是数位dp的最精髓操作,也是最难的一部分(其实现在看来思路也满清晰的),其实就是下一位能不能乱取0~9,先想想什么时候不能,就是当前面所有的值都取到了最高位,你有一个数字4751,前面都去了475了,剩下就只有0和1,但是如果前面取的474,那么0到9都可以随便选,都不会超过4751。
所以我们可以发现:
若当前位 limit = 1, 而且已经取到了能取到的最高位时,下一位 limit = 1;
若当前位 limit = 1, 但是没有取到能取到的最高位时,下一位 limit = 0 ;
若当前位 limit = 0时,下一位 limit = 0 。
那么总结出下一位的limit的状态:lim && upp==i
最后按着思路写就能AC
#include<bits/stdc++.h>
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 100005
#define maxm 55
#define hrdg 1000000007
#define zh 16711680
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define int long long
using namespace std;
char L[maxn], R[maxn];
int lenl, lenr;
int ans1, ans2, ans, dp[maxn][5];
int numl[maxn], numr[maxn], prel[maxn], prer[maxn];
int key[5] = {0, 4, 7, 5, 0};
int Qpow(int a, int x){ //我tm一直wa的一个原因是把快速幂的函数名定义成了pow就和系统自带的那个重了
int ret = 1; //系统那个因为是浮点数运算有精度误差,nmd这个bug我找了好久,想起来气死了
while(x)
{
if(x & 1)
ret = ret * a % zh;
a = a * a % zh;
x >>= 1;
}
return ret % zh;
}
void init(){
for (int i = lenl; i >= 1; i--)
{
numl[i] = (L[i] - '0') * Qpow(10, lenl - i);
if(i < lenl)
numl[i] = (numl[i] + numl[i + 1]) % zh;
prel[i] = Qpow (10, lenl - i + 1);
}
for (int i = lenr; i >= 1; i--)
{
numr[i] = (R[i] - '0') * Qpow(10, lenr - i);
//printf("i = %d num = %d\n", i, numr[i]);
if(i < lenr)
numr[i] = (numr[i] + numr[i + 1]) % zh;
prer[i] = Qpow (10, lenr - i + 1);
}
}
int dfs(int pos, int now, int lim, char *s, int pre[], int num[],int len){
if (pos > len)
return now == 4;
if (!lim && dp[pos][now] != -1)
return dp[pos][now];
int ret = now == 4;
int now1 = now;
now = now == 4 ? 0 : now;
if(!lim)
{
//if(ret) printf("pre[pos] = %d pos = %d now = %d ret = %d\n", pre[pos], pos, now1, ret);
ret = ret * pre[pos] % zh;
}
else
{
//if(ret) printf("pos = %d now = %d ret = %d\n", pos, now1, ret);
ret = ret * (num[pos] + 1) % zh;
}
int upp = lim ? s[pos]-'0' : 9;
for (int i = 0; i <= upp; i++)
{
if (key[now + 1] == i)
ret += dfs(pos + 1, now + 1, lim && upp==i, s, pre, num, len);
else if (key[1] == i && now != 0)
ret += dfs(pos + 1, 1, lim && upp==i, s, pre, num, len);
else
ret += dfs(pos + 1, 0, lim && upp==i, s, pre, num, len);
ret %= zh;
}
if (!lim)
dp[pos][now1] = ret % zh;
//printf("lim = %d pos = %d now = %d ret = %d\n", lim, pos, now1, ret);
return ret % zh;
}
signed main()
{
scanf("%s %s", L + 1, R + 1);
lenl = strlen(L + 1);
lenr = strlen(R + 1);
init();
//FOR(i, 1, lenr) printf("%d\n", numr[i]);
//FOR(i, 1, lenr) printf("%d\n", prer[i]);
memset(dp, -1, sizeof(dp));
ans1 = dfs(1, 0, 1, L, prel, numl, lenl);
//puts("");
memset(dp, -1, sizeof(dp));
ans2 = dfs(1, 0, 1, R, prer, numr, lenr);
for (int i = 1; i <= lenl - 3; i++)
{
if(L[i] == '4' && L[i + 1] == '7' && L[i + 2] == '5' && L[i + 3] == '0')
ans++;
}
//printf("ans1 = %lld ans2 = %lld\n", ans1, ans2);
ans = ((ans % zh + ans2) % zh - ans1 + zh) % zh;
cout << ans;
return 0;
}