动态规划专题 - 解题报告 - M

别的我不说了,良心题解先吹一波好吧。
最重要的使这一段

假如第一位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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值