区间dp

本文介绍了一种使用区间动态规划方法解决特定数学问题的技巧,即求解一个长度不超过50的数字串中,有多少个子序列构成的数字可以被3整除。文章详细解释了状态定义、状态转移方程,并提供了完整的C++实现代码。

对于牛客的竞赛培训 动态规划的第一题

就给我来了个区间dp 的题 这个也太猛了吧

废话不多说  直接上 题

链接:https://ac.nowcoder.com/acm/problem/21302
来源:牛客网
 

题目描述

给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除
答案对1e9+7取模

输入描述:

输入一个字符串,由数字构成,长度小于等于50

输出描述:

输出一个整数

在这之前我没接触过区间dp的题目  这个题算是自己的区间dp 的一个开端吧

但是我也是看大佬的博客所理解的  所以 如果说的不好 或者不清楚 希望大佬们在评论区 给我反馈

好了 说这个题吧

因为是子序列 所以可以不连续(所以用区间dp的方法做这个题)

因此需要保存个子序列对3取余 余数为0 1 2 的时候

这里我们用dp[i][k]表示 从0到i的区间上 余数为k 的数目  

然后我们再说状态转移方程

如果扩充一个数 该数为s[i]%3==1 那么下一个

dp[i][0]=dp[i-1][2]+dp[i-1][0]

dp[i][1]=dp[i-1][1]+dp[i-1][0]+1

dp[i][2]=dp[i-1] [1]+dp[i-1][2];

如果扩充一个数 该数为s[i]%3==2 那么下一个

dp[i][0]=dp[i-1][0]+dp[i-1][1]

dp[i][1]=dp[i-1][1]+dp[i-1][2]

dp[i][2]=dp[i-1][0]+dp[i-1][2]+1

如果扩充一个数 该数为s[i]%3==0 那么下一个

dp[i][0]=2*dp[i-1][0]+1

dp[i][1]=2*dp[i-1][1]

dp[i][2]=2*dp[i-1][2]

并且每次循环后需要对dp[i][j]取余 mod 防止越界

最后答案为dp[n-1][0] 表示0到n-1的区间上能被3整除的数的个数

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define db double
#define MAX 1000000
#define rep(i,j,k) for(int i=(int)(j);i<=(int)(k);i++) 
#define per(i,j,k) for(int i=(int)(j);i>=(int)(k);i--)
long long mod = 1e9 + 7;
ll dp[55][10];
int main()
{
	string s;
	cin >> s;
	//区间dp
	//状态转移方程:
	/*
	dp[i][0]=dp[i-1][0]+dp[i-1][2];
	dp[i][1]=dp[i-1][0]+dp[i-1][1]+1;
	dp[i][2]=dp[i-1][1]+dp[i-1][2];
	*/
	//初始dp全部为1
	for (int i = 0; i < s.length(); i++) {
		dp[i][(s[i] - '0') % 3] = 1;
	}
	for (int i = 1; i < s.length(); i++) {
		if ((s[i] - '0') % 3 == 0) {
			dp[i][0] = 2 * dp[i - 1][0] + 1; //原本余数为0  加上一位后 除了是之间的两倍还需要加上之间本身的1
			dp[i][1] = 2 * dp[i - 1][1];
			dp[i][2] = 2 * dp[i - 1][2];
		}
		else if ((s[i] - '0') % 3 == 1)
		{
			dp[i][0] = dp[i - 1][0] + dp[i - 1][2];
			dp[i][1] = dp[i - 1][0] + dp[i - 1][1] + 1;
			dp[i][2] = dp[i - 1][1] + dp[i - 1][2];
		}
		else
		{
			dp[i][0] = dp[i - 1][1] + dp[i - 1][0];
			dp[i][1] = dp[i - 1][1] + dp[i - 1][2] ;
			dp[i][2] = dp[i - 1][2] + dp[i - 1][0] + 1;
		}
		for (int j = 0; j < 3; j++) {
			dp[i][j] %= mod;
		}
	}
	cout << dp[s.length()-1][0] << endl;
	//system("pause");
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值