头条真的很难!
思路来自于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;