【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]\)已算完。

则有

\[\begin{cases} dp[i][n-i+1]=1\\ dp[i][j]= \begin{cases} dp[i+j][j], & \text{if }v[i,i+j-1] & < & v[i+j,i+2\times j-1]\\ dp[i+j][j+1], & \text{if }v[i,i+j-1] & \geqslant & v[i+j,i+2\times j-1] \end{cases} +dp[i][j+1] \end{cases} \]

解释一下状态转移方程。

首先第一条柿子\(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就是个傻逼却又毒瘤的东西

posted @ 2022-10-30 10:40  ez_lcw  阅读(14)  评论(0编辑  收藏  举报