题面
Description
【题目描述】
给定一个长度为n的由['0'..'9']组成的字符串s,v[i,j]表示由字符串s第i到第j位组成的十进制数字。
将它的某一个上升序列定义为:将这个字符串切割成m段不含前导'0'的串,切点分别为k1,k2...km-1,使得v[1,k1]<v[k1+1,k2]<...<v[km-2,km-1]。
请你求出该字符串s的上升序列个数,答案对 10^9+7 取模。
【输入数据】
第一行一个整数n,表示字符串长度;
第二行n个['0'..'9']内的字符,表示给出的字符串s。
【输出数据】
仅一行表示给出字符串s的上升序列个数对10^9+7取模的值。
【样例输入1】
6
123434
【样例输出1】
8
【样例输入2】
8
20152016
【样例输出2】
4
【数据范围】
对于30%的数据满足:n<=10;
对于100%的数据满足:n<=5000。
题意
一个有n个数的序列,将它分成若干段,要求每段不含有前导零,且分段后形成的m个数单调递增。
题解
对于30%的数据,看到这么小的n,当然是大暴力啦hhhhhh
#include<iostream>
using namespace std;
int n,ans;
char ch[30];
unsigned long long toi(int l,int r){//计算分出来的数
unsigned long long res=0;
for(register int i=l;i<=r;i++)res=res*10ll+(unsigned long long)ch[i]-'0';
return res;
}
void dfs(int u,int lp,unsigned long long li){
if(u==n+1){
ans++;
return;
}
unsigned long long num=toi(lp+1,u);
if(num>li&&ch[u+1]!='0')dfs(u+1,u,num);//注意前导零
if(u!=n)dfs(u+1,lp,li);
}
int main(){
// freopen("1.txt","r",stdin);
scanf("%d%s",&n,ch+1);
dfs(1,0,0);
printf("%d",ans);
}
100%的数据,考虑dp。
看到序列,自然想到关于序列的东西,一番摸索后发现可以用lcp做。
设$lcp[i][j]$为$i$下标开始的后缀和$j$下标开始的后缀的lcp。
然后枚举断点和区间长度,有请dp
设$f[i][j]$为将$[i,i+j)$作为一段可行的方案数,将$[i,i+j)$分为一段后,下一段的起点为$i+j$,长度>=$j$,得到递推式:
如果当前段比下一段小,$f[i][j]=f[i+j][j]+f[i+j][j+1]+......+f[i+j][n-i]$
否则,$f[i][j]=f[i+j][j+1]+f[i+j][j+2]+......+f[i+j][n-i]$
比较大小可以通过lcp快速得出,而递推可以通过维护后缀和达到$O(n^{2})$
#include<iostream>
using namespace std;
int n,f[5005][5005],lcp[5005][5005],mod=1e9+7;
char ch[5005];
int main(){
scanf("%d%s",&n,ch+1);
for(int i=n;i>=1;i--){//lcp预处理
for(int j=i+1;j<=n;j++){
if(ch[i]==ch[j])lcp[i][j]=lcp[i+1][j+1]+1;
}
}
for(int i=n;i>=1;i--){
if(ch[i]=='0')continue;//前导零
f[i][n-i+1]=1;
for(int j=1;j<=n-i;j++){
int t=min(lcp[i][i+j],j-1);
if(ch[i+t]<ch[i+j+t])f[i][j]=f[i+j][j];//比较
else f[i][j]=f[i+j][j+1];
}
for(int j=n-i;j>=1;j--)f[i][j]=(f[i][j]+f[i][j+1])%mod;//后缀和
}
printf("%d",f[1][1]);
}