sequence 校内woj4320 【dp+hash二分+前缀和优化】

题面

///

给定一个长度为nn 的由[′0′..′9′][′0′..′9′] 组成的字符串ss ,v[i,j]v[i,j] 表示由字符串ss 第ii 到第jj 位组成的十进制数字。

将它的某一个上升序列定义为:将这个字符串切割成mm 段不含前导′0′′0′ 的串,切点分别为k1,k2...km−1k1,k2...km−1 ,使得v[1,k1]<v[k1+1,k2]<...<v[km−2,km−1]v[1,k1]<v[k1+1,k2]<...<v[km−2,km−1] 。

请你求出该字符串ss 的上升序列个数,答案对1e9+7取模。
///

 

30分:深搜。

100分:dp

注意状态的定义
  由于 前后分割情况不同,匹配需要更多信息;
  那么,可以选择存 f[i][j],表示  取i-j为最后一段的总方案数。

(方案数统计不带修改不难想到dp,又有字串的·处理,加上信息不够,可以想到 用区间l  r  来存储信息的形式)

这个时候,容易想到,

之前的    k~i-1中,只要i长度(位数)小于当前的长度(位数),

一定是严格小于的。

 

只需要考虑,k~i-1   长度等于   i~j    

即   k=i-1-(j-i+1)-1=2*i-j-1时,

如果前面的·字符串小于i-j就成立

 

比较字符串大小当然从大的位数开始。
这时要想到字符串比较方式  ,不难想到hash
但是hash只能处理字符串相同的情况(玄学处理),无法得出严格的大小关系
不难想到用二分找出第一位不同的,比较字符即可

 

 

最后注意处理f[k][i-1]总和(k--  2*i-j~i-1),不难想到用前缀和维护
f[][]维护成前缀和
注意判‘0’    即i==0方案书为0(这个反而简单)

最后答案刚好是 1-n  n 的前缀和。

 


#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int maxn=5e3+10,mod=1e9+7;

int v[maxn][maxn],f[maxn][maxn];

char s[maxn];

bool check(int a,int b,int c,int d){
//	if(s[a]!=s[c])return (s[a]<s[c])? 1:0;
	if(v[a][b]==v[c][d])return 0;
	int l=0,r=b-a,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(v[a][a+mid]!=v[c][c+mid])r=mid;
		else l=mid+1;
	}
	if(s[a+l]<s[c+l])return 1;
	return 0;
}

int n;
signed main(){
	scanf("%lld",&n);
	scanf("%s",s+1);
	for(int i=1;i<=n;++i)
		for(int j=i;j<=n;++j){
			v[i][j]=(v[i][j-1]*10+s[j]-'0')%mod;
		}
	for(int j=1;j<=n;++j)f[1][j]=1;
	//注意方案要初始化成1还没有开始算前缀和 
	for(int i=2;i<=n;++i){
		for(int j=i;j<=n;++j){
			if(s[i]=='0')f[i][j]=0;
			else{
				int r=i-1,l=i+i-j-1;
				f[i][j]=((f[r][r]-f[max(0ll,l)][r])%mod+mod)%mod;
				if(l>0&&check(l,r,i,j)){
					f[i][j]=(f[i][j]+((f[l][r]-f[l-1][r])%mod+mod)%mod)%mod;
				}
			}
			f[i][j]=(f[i][j]+f[i-1][j])%mod;
		}
	}
	printf("%lld",f[n][n]);
	return 0;
	
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值