头条笔试0825-合法表达式

本文介绍了如何使用动态规划解决头条笔试中关于合法表达式的题目。通过枚举最后一个加减号的位置来避免重复计算,同时讨论了dp数组的更新策略,包括以数字和括号结尾的合法表达式情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

头条真的很难!
这里写图片描述
思路来自于https://www.nowcoder.com/discuss/98697中Gauss的回答,粘在下面:

动态规划求解, dp[i]表示长度为i的合法字符串, dp2[i]表示长度为i的符合以下两种形式的的合法表达式, 形式一:全是数字, 形式二: (合法表达式), 维护dp[i]的时候,考虑最后一个加号或者减号出现的位置j, dp[i]加上2LL*dp[j]*dp2[i-1-j],因为枚举的是最后一个加减号的位置,因此右边只能是全数字或者(合法表达式)。为什么要枚举最后一个加减号,就是为了避免重复计算,例如1+2+3,如果不枚举最后一个加号,当枚举到1+2的加号时,左边1和右边2+3可行,当枚举到2+3的加号时,左边1+2和右边3也可行,这样1+2+3会被计算两次。
原作者代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int mod=1000000007;
int dp[1010],f[1010],dp2[1010];
int main()
{
    int n;
    cin>>n;
    f[0]=1;
    for(int i=1;i<=1000;i++) f[i]=10LL*f[i-1]%mod;
    dp2[0]=dp[0]=0,dp2[1]=dp[1]=10;
    for(int i=2;i<=n;i++){
        dp[i]=dp[i-2];
        dp[i]=(dp[i]+f[i])%mod;
        dp2[i]=dp[i];
        for(int j=1;j<i;j++)
            dp[i]=(dp[i]+2LL*dp[j]*dp2[i-1-j])%mod;
    }
    cout<<dp[n]<<endl;
    return 0;
}

根据我的理解写一下:
首先:dp2[i]=dp[i-2]+f[i],这很好理解,dp2[i]表示形如:(1)(合法表达式)或(2)全数字表达式 长度为i的表达式个数。
其次是:dp[i]的计算。dp[i]包括(1)dp2[i] (2)合法表达式+合法表达式 (3)合法表达式-合法表达式 三部分组成,所以dp[i]一开始等于dp2[i],然后在此基础上加上(2)和(3)两种形式。
最后:对于上述步骤中的(2)和(3)两种形式,维护dp[i]的时候,考虑最后一个加号或者减号出现的位置j, dp[i]加上2LL*dp[j]*dp2[i-1-j],因为枚举的是最后一个加减号的位置,因此右边只能是全数字或者(合法表达式)。至于为什么考虑最后一个加号或减号出现的位置,参见原作者的解释。

我把代码修改了一下方便自己理解:

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

const int mod=1000000007;
int dp[1010],f[1010],dp2[1010];
int main()
{
    int n;
    cin>>n;
    f[0]=1;
    cout<<"10LL:"<<10LL<<endl;
    for(int i=1;i<=1000;i++) 
        f[i]=10LL*f[i-1]%mod;
    for(int i=0;i<=15;i++)
        cout<<f[i]<<" ";
    cout<<endl;
    dp2[0]=dp[0]=0,dp2[1]=dp[1]=10;
    for(int i=2;i<=n;i++){
        dp2[i]=(dp[i-2]+f[i])%mod;
        dp[i]=dp2[i];
        for(int j=1;j<i;j++)
        {   
            dp[i]=(dp[i]+2LL*dp[j]*dp2[i-1-j])%mod;
        }
    }
    cout<<dp[n]<<"   "<<dp2[n]<<endl;
    return 0;
}

又看到一个解法,大神果然是大神,粘贴至此以备学习:

#include <iostream>
using namespace std;
long long dp[1002];//存以数字结尾的合法表达式
long long dp2[1002];//存以括号结尾的合法表达式
int main() {
    int a;
    for(int i = 0;i<1002;i++)
    {
        dp[i] = 0;
        dp2[i] = 0;
    }
    dp[0] = 0;
    dp[1] = 10;
    dp[2] = 100;
    dp[3] = 200+1000;
    dp2[0] = 0;
    dp2[1] = 0;
    dp2[2] = 0;
    dp2[3] = 10;
    while(cin >> a)//注意while处理多个case
    {
        //dp2
        for(int i = 4;i<=a;i++)
        {
            for(int j = i-2;j>=3;j--)
            {
                long long d = (dp[i-j-1]+dp2[i-j-1])%1000000007;
                long long b = (dp[j-2]+dp2[j-2])%1000000007;
                dp2[i] += (2*b*d)%1000000007;
            }
            dp2[i] += (dp[i-2]+dp2[i-2])%1000000007;
            dp[i] = (dp[i-1] + 2*(dp[i-2]+dp2[i-2]))*10%1000000007;
        }

        cout<<((dp[a]+dp2[a])%1000000007)<<endl;
    }
}

这段代码中dp[i]表示以数字结尾的合法字符串;dp2[i]表示以括号结尾的合法字符串大概是这种形式:(合法表达式),而不是我们(合法表达式)+(合法表达式)的形式。
因此在递推dp2[i]时,需要考虑括号中间有类似 (合法表达式)+(合法表达式)的形式,这部分就是固定最后一个加号或减号遍历,和解法一的思路一样,代码中就是dp2[i] += (2*b*d)%1000000007;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值