牛客NC21302被3整除的子序列

文章描述了一种使用动态规划方法解决编程问题的策略,特别是关于找出一个数字序列中所有能被3整除的子序列的数量。通过维护对3取余的三种状态,初始化、状态转移方程和结果表示是DP的核心部分。给出的AC代码展示了如何实现这个算法。
摘要由CSDN通过智能技术生成

题目链接

被3整除的子序列 (nowcoder.com)

题目描述

思路

子序列定义:某个序列的子序列是在原序列的基础上去掉一些元素(或不去)但不破坏剩下元素的相对位置的序列(以原序列abcde为例,去掉c元素,原序列的子序列就是abde,而abed就不是,因为它破坏了原序列的相对位置)。

那么此题的每位上的元素就可以看成取和不取两种状态,题目问的是被3整除的子序列数,且能被3整除的数各个位数字之和对3取余为0,所以每次判断完一位数之后只需记录余数为0,1,2这3种状态即可。这一想直接拿DP做:创建dp[51][3],dp[ i ][ j ]的值的含义是处理前i个元素之后,各位数之和对3取余以后子序列的个数,那dp[ i ][0]就是前 i 位数的子序列可以被3整除的个数。长度为n的序列的可被3整除的子序列个数就是dp[ n ][0].

DP的核心无非3要素:1.DP的初始化;2.状态转移方程;3.结果的表示。

A.DP的初始化:处理到第 i 位时,本位元素对3取余,记余数为m(方便下文解释和理解),然后dp[ i ][m]赋值为1,此步的意思是第 i 个元素做子序列的头。

B.DP的状态转移:处理到第 i 位时,做完A步(其实这题A步骤也可以看成状态转移的一部分)后,遍历dp[i][0~2],不同于A的是,此次将第 i 个元素看作是子序列的尾端。根据上述提到的,第 i 个数分为两种情况:(1)第 i 个数不加入子序列:那么本位就继承上一位的数据,即dp[ i ][?]+=dp[ i-1 ][?];(2)第 i 个数加入子序列:处理到dp[ i ][ j ]时,因为本位对3取余已有m,所以对于余数为 j 时,只需继承前数余数的j-m就行,但是考虑到j-m可能小于0,所以j-m替换为(j-m+3)%3,这样同时也保证了j-m+3不会超过2。整合起来状态转移方程就是dp[ i ][ j ]+=(dp[ i-1 ][ j ]+dp[i-1][(j+3-m)%3])%inf;(inf为题目的结果取余量,即1e9+7)。

C.结果表示为dp[ n ][0](上已述,此不赘述)。

AC代码

#include<bits/stdc++.h>
using namespace std;
#define maxn 51//数字串最大的长度
#define inf 1000000007//取模数
int n,m,dp[maxn][3];//n表示数字串长度,m用来存储余数,dp[i][j]的值的含义是处理前i个元素之后,各位数之和对3取余以后子序列的个数
string s;//接收数字串
int main(){
	cin>>s;//输入数字串
	n=s.size();//求出数字串的长度
	for(int i=1;i<=n;i++){//i表示处理到第几个元素
		m=(s[i-1]-'0')%3;//m记录本位对3取余
		dp[i][m]+=1;//将本位做子序列的开头
		for(int j=0;j<=2;j++){//本位各种情况记录
			dp[i][j]+=(dp[i-1][j]+dp[i-1][(j+3-m)%3])%inf;//dp[i-1][j]:本位不算入前面子序列尾端;dp[i-1][(j+3-m)%3]表示本位算入前面子序列尾端
		}
	}
	cout<<dp[n][0];//输出结果
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Felix_wjl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值