【XSY2564】sequence(dp)
题面
【题目描述】
给定一个长度为\(n\)的由\([\text{'}0\text{'}...\text{'}9\text{'}]\)组成的字符串\(s\),\(v[i,j]\)表示由字符串\(s\)第\(i\)到第\(j\)位组成的十进制数字。
将它的某一个上升序列定义为:将这个字符串切割成\(m\)段不含前导\(\text{'}0\text{'}\)的串,切点分别为\(k_1\),\(k_2\)...\(k_{m-1}\),使得\(v[1,k_1]<v[k_1+1,k_2]<...<v[k_{m-2}+1,k_{m-1}]\)。
请你求出该字符串\(s\)的上升序列个数,答案对 \(10^9+7\) 取模。
【输入数据】
第一行一个整数\(n\),表示字符串长度;
第二行\(n\)个\([\text{'}0\text{'}...\text{'}9\text{'}]\)内的字符,表示给出的字符串\(s\)。
【输出数据】
仅一行表示给出字符串s的上升序列个数对\(10^9+7\)取模的值。
【样例输入1】
6
123434
【样例输出1】
8
【样例输入2】
8
20152016
【样例输出2】
4
【数据范围】
对于\(30\%\)的数据满足:\(n<=10\);
对于\(100\%\)的数据满足:\(n<=5000\)。
题解
以下的\(a[l,r]\)均表示\(a\)数组(串)的第\(l\)位到第\(r\)位。
设\(dp[i][j]\)表示第一段的开头为\(i\),第一段长度为\(j\sim n-i+1\)的所有方案数。(我们先假设第一段的头不是\(1\))
则最后的答案为\(dp[1][1]\)(第一段的开头为\(1\),第一段长度为\(1\sim n\)的所有方案数)。
再设\(maxn[i][j]\)等于\(s[i,n]\)与\(s[j,n]\)的最长公共前缀。
即使得\(s[i,i+maxn[i][j]-1]=s[j,j+maxn[i][j]-1]\)且\(maxn[i][j]\)最大。
那么我们逆序枚举\(i\),再逆序枚举\(j\),即\(dp[i+1,n]\)和\(dp[i][j+1,n-i]\)已算完。
则有
解释一下状态转移方程。
首先第一条柿子\(dp[i][n-i+1]=1\)很好理解吧,即整个串只有一段的时候就一种方案。
然后第二条柿子的第一小条,即
\(dp[i][j]=dp[i+j][j]+dp[i][j+1],\text{if }v[i,i+j-1]<v[i+j,i+2\times j-1]\)
意思就是,当这两个长度相等且相邻的两个串\(A=v[i,i+j-1]\)、\(B=v[i+j,i+2\times j-1]\),\(A<B\),那么当\(A\)为第一段的串时,显然是满足条件的。且如果往\(B\)后面不断增加任意数,还是满足\(A<B\),所以要加上\(dp[i+j][j]\)(不知道为什么只用加上\(dp[i+j][j]\)的注意\(dp[i+j][j]\)的定义)。
然后为了满足\(dp[i][j]\)的定义,我们需要加上长度为\(j\sim n-i+1\)的方案数,即加上\(dp[i][j+1]\)(由于\(dp[i][j+1]\)的定义,\(dp[i][j+1]\)已经把长度为\(j+1\sim n-i+1\)的方案数算上了)。
第二条柿子的第二小条也差不多,只不过是当\(A\geqslant B\)时(注意这里的\(\geqslant\)并不是按字典序,而是按它们所对应的十进制数字排序,所以当\(len(S_1)<len(S_2)\)时,无论如何都是\(S_1<S_2\))。那么如果第一段为\(A\),第二段为\(B\)显然不满足要求,但\(len(A)=len(B)\),所以如果往\(B\)后面不断增加任意数,也会满足\(A<B\),所以要加上\(dp[i+j][j+1]\)。
至于为什么要加上\(dp[i][j+1]\)和第一小条的理由相同。
那么我们就可以用这个状态转移方程把答案求出来了。
但我们怎么快速(\(O(1)\))比较串\(A[l_1,r_1]\)与串\(B[l_2,r_2]\)的大小呢?
注意:\(A\)、\(B\)满足\(len(A)=len(B)\)且\(r_1+1=l_2\)(即\(A\)、\(B\)相邻且长度相等)
这时就要用到\(maxn\)了,不记得的先看一下上面的定义。
那么\(A\)、\(B\)的最长公共前缀长度即为:\(same\_len=min(maxn[l_1][l_2],r_1-l_1+1)\)。
我们只需比较\(s[l_1+same\_len]\)和\(s[l_2+same\_len]\)就好了。
但这样还是不能过……
因为如果\(same\_len\)真的等于\(r_1-l_1+1\),即\(A=B\),程序就会比较\(s[l_1+same\_len]=s[l_1+(r_1-l_1+1)]=s[r_1+1]=s[l_2]\)与\(s[l_2+same\_len]=s[l_2+(r_2-l_2+1)]=s[r_2+1]\),即\(s[l_2]\)与\(s[r_2+1]\),但如果\(A=B\)我们不能再比较下去,只能返回\(A>=B\)。
所以我们将\(same\_len\)设为\(same\_len=min(maxn[l_1][l_2],r_1-l_1)\),这样如果\(same\_len\)真的等于\(r_1-l_1\),也只会比较\(s[r_1]\)与\(s[r_2]\),程序就会判断出\(A=B\)。
为什么一个特判能搞定的事情我要讲这么两段
最后代码如下:
#include<bits/stdc++.h>
#define N 5010
#define mod (int)(1e9+7)
using namespace std;
int n,dp[N][N],maxn[N][N];
char s[N];
void add(int &x,int y)
{
x=(x+y)%mod;
}
int main()
{
scanf("%d%s",&n,s+1);
for(int i=n;i;i--)
for(int j=i+1;j<=n;j++)
if(s[i]==s[j])
maxn[i][j]=maxn[i+1][j+1]+1;//求maxn,自己理解一下
for(int i=n;i;i--)
{
if(s[i]=='0')continue;//记得特判0
dp[i][n-i+1]=1;//记得初始化
for(int j=1;j<=n-i;j++)
{
int same=min(j-1,maxn[i][i+j]);
if(s[i+same]<s[i+j+same])//分两种情况
dp[i][j]=dp[i+j][j];
else
dp[i][j]=dp[i+j][j+1];
}
for(int j=n-i;j;j--)
add(dp[i][j],dp[i][j+1]);//题解里讲的是边求出dp[i][j]边加dp[i][j+1],其实最后加也可以
}
printf("%d\n",dp[1][1]);
return 0;
}
恕我直言:dp就是个傻逼却又毒瘤的东西