CF611D 【分割字符串使得形成的数字数组呈递增状态】的方法数

47 篇文章 0 订阅

这题比较巧妙的是运用最长公共前缀长度简化问题以及运用sum数组缩减复杂度。注意要求分割后所有数字不得出现前导零。

题解:点击打开链接

没用sum数组的超时代码:

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
int lcp[5001][5001];
ll dp[5001][5001];
int main(){
	int n;
	cin>>n;
	string x;
	cin>>x;
	x='0'+x;	
	for(int i=n;i>=1;--i){
		for(int j=n;j>i;--j){
			if(x[i]==x[j])
				lcp[i][j]=lcp[i+1][j+1]+1;
		}
	}
	for(int i=1;i<=n;++i)
		dp[0][i]=1;
	for(int i=1;i<=n;++i){             //这个子串的结尾位置 
		for(int j=1;j<=i;++j){         //dp[i][j]:以【从x[i]往前j长度的子串】结尾的可能数 
			int p=i-j+1;               //这个子串的开头位置 
			if(x[p]=='0') continue;
			for(int k=p-1;k>=max(p-j,0);--k){ //枚举上一个子串的开头位置(上一个一定不能比这个长)
				char a=x[k+lcp[k][p]],b=x[p+lcp[k][p]];
				if(k!=p-j||(lcp[k][p]<j&&a<b))
					dp[i][j]=(dp[i][j]+dp[p-1][p-k])%mod; 
			}
		}
	}
	ll s=0;
	for(int i=1;i<=n;++i){
		s=(s+dp[n][i])%mod;
	}
	cout<<s<<endl;
	return 0;
}

AC代码:

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
int lcp[5001][5001];
ll dp[5001][5001];
ll sum[5001][5001];
int main(){
	int n;
	cin>>n;
	string x;
	cin>>x;
	x='0'+x;	
	for(int i=n;i>=1;--i){
		for(int j=n;j>i;--j){
			if(x[i]==x[j])
				lcp[i][j]=lcp[i+1][j+1]+1;
		}
	}
	for(int i=0;i<=n;++i)
		sum[0][i]=1;
	for(int i=1;i<=n;++i){             //这个子串的结尾位置 
		for(int j=1;j<=i;++j){         //dp[i][j]:以【从x[i]往前j长度的子串】结尾的方法数 
			int p=i-j+1;               //这个子串的开头位置 
			if(x[p]=='0') continue;
//			for(int k=p-1;k>=max(p-j,0);--k){ //枚举上一个子串的开头位置(上一个一定不能比这个长)
//				char a=x[k+lcp[k][p]],b=x[p+lcp[k][p]];
//				if(k!=p-j||(lcp[k][p]<j&&a<b))
//					dp[i][j]=(dp[i][j]+dp[p-1][p-k])%mod; 
//			}
			dp[i][j]=sum[p-1][j-1]; //sum[i][j]表示符合当前【最后一个的长度小于j】的方法数(上一个比这个短当然较小) 
			
			//考虑完上一个比这个短的情况,接下来只需要考虑长度相等的情况 
			int k=p-j;
			if(k<1)
				continue;
			char a=x[k+lcp[k][p]],b=x[p+lcp[k][p]];
			if(lcp[k][p]<j&&a<b)
				dp[i][j]=(dp[i][j]+dp[p-1][j])%mod; 
		}
		for(int j=1;j<=n;++j){ //这里注意是【<=n】而不是【<=i】,因为dp[i][j]=sum[p-1][j-1];会用到之前的状态(p-1),j-1可能大于p-1 
			sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
		}
	}
	ll s=0;
	for(int i=1;i<=n;++i){
		s=(s+dp[n][i])%mod;
	}
	cout<<s<<endl;
	return 0;
}

情况里可以出现前导零,即没有考虑到【要求分割后所有数字不得出现前导零】的代码(读漏题目的后果):

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
int lcp[5001][5001];
ll dp[5001][5001];
int main(){
	int n;
	cin>>n;
	string x;
	cin>>x;
	x='0'+x;	
	for(int i=n;i>=1;--i){
		for(int j=n;j>i;--j){
			if(x[i]==x[j])
				lcp[i][j]=lcp[i+1][j+1]+1;
		}
	}
	for(int i=1;i<=n;++i)
		dp[0][i]=1;
	for(int i=1;i<=n;++i){             //这个子串的结尾位置 
		int w=i;
		for(int j=1;j<=i;++j){         //dp[i][j]:以【从x[i]往前j长度的子串】结尾的方法数 
			int p=i-j+1;               //这个子串的开头位置 
			if(x[p]!='0')
				w=p;                   //这个子串的【非0】开头位置 
			int  h=p-1;
			for(int k=p-1;k>=0;--k){   //枚举上一个子串的开头位置(上一个一定不能比这个长,错!比如0012,001比2长但比2小)
				if(x[k]!='0')
					h=k;               //这个子串的【非0】开头位置 
				char a=x[h+lcp[h][w]],b=x[w+lcp[h][w]];
				if(p-h<i-w+1||(p-h==i-w+1&&lcp[h][w]<j&&a<b)) //【这个串较长】或【一样长比较公共序列后第一个不同字符】 
					dp[i][j]=(dp[i][j]+dp[p-1][p-k])%mod; 
			}
		}
	}
	ll s=0;
	for(int i=1;i<=n;++i){
		s=(s+dp[n][i])%mod;
	}
	cout<<s<<endl;
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值