【数位dp】3704. 统计和为 N 的无零数对|2419
本文涉及知识点
C++动态规划 数位dp
3704. 统计和为 N 的无零数对
一个 无零 整数是一个十进制表示中 不包含数字 0 的 正 整数。
给定一个整数 n,计算满足以下条件的数对 (a, b) 的数量:
a 和 b 都是 无零 整数。
a + b = n
返回一个整数,表示此类数对的数量。
示例 1:
输入: n = 2
输出: 1
解释:
唯一的数对是 (1, 1)。
示例 2:
输入: n = 3
输出: 2
解释:
数对有 (1, 2) 和 (2, 1)。
示例 3:
输入: n = 11
输出: 8
解释:
数对有 (2, 9)、(3, 8)、(4, 7)、(5, 6)、(6, 5)、(7, 4)、(8, 3) 和 (9, 2)。请注意,(1, 10) 和 (10, 1) 不满足条件,因为 10 在其十进制表示中包含 0。
提示:
2<=n<=10152 <= n <= 10^{15}2<=n<=1015
数位dp
性质一:由于任意数位都不能是0,故所有所有位的范围都是’1’到’9’。为了避免考虑不同的位数,容许所有位为0,非前导0,在枚举时抛弃。
性质二:0≤a≤n,a+b=n,→0≤b≤n0 \le a \le n,a+b=n, \rightarrow 0 \le b \le n0≤a≤n,a+b=n,→0≤b≤n,枚举0≤n0 \le n0≤n的a的数量,a无0且n-b无零,不包括前导0。b一定≥0\ge 0≥0。需要扣除(0,n)和(n,0)。如果n的字符串形式包括非前导0,则无需扣除。避免重复扣除。
封装类
dp[n] 表示当前字符串长度为n的前缀不是上限的方案数,upDp[n]表示当前字符串长度为n的前缀是上限的方案数。
故我们只需要考虑自定义状态m。m&4 表示后N-n位是否有进位,m&2表示a是否有前导0,m&1表示b是否有前导0。如果b有前导零,则b的当前位必须是0。
实现
用m枚举下一位的状态,用carrycur 枚举当前位(第n位)是否进位。carrynext 表示下一位(第n+1位)是否进位。a表示a的当前位,b表示b的当前位。则(carrycur?10:0)+strN[n]−′0′=a+b+carrynext(carrycur?10:0)+strN[n]-'0'=a+b+carrynext(carrycur?10:0)+strN[n]−′0′=a+b+carrynext。
如果 b < 0 忽略。
如果(m&2)且a>0,忽略。
如果(m&1)且b>0,忽略。
当前位自定义状态m1 = 8carrycur+4(0= =a)+2*(0= =b)
合法的初始状态:carry 非0。
合法的终止状态:dp[0]=upDp[0]=1,其它0。
代码
核心代码
template<class ELE, class ResultType>
class IUpperDPCall
{
public:virtual void OnEnum(int n,vector<ResultType>& dp, const vector<ResultType>& vNext, ELE curValue, int iCustomStatusCount) = 0;virtual void OnInitEnd(vector<ResultType>& dp, vector<ResultType>& upDp) = 0;
};template<class ELE, class ResultType>
class CUperrDP
{
public:CUperrDP(int iCustomStatusCount, IUpperDPCall<ELE, ResultType>& call, ELE minEle, ELE maxEle):m_iCustomStatusCount(iCustomStatusCount),m_call(call),m_minEle(minEle),m_maxEle(maxEle){}void Init(const ELE* pHigh, int iEleCount){m_vDP.assign(iEleCount + 1, vector<ResultType>(m_iCustomStatusCount));m_vDpUpper = m_vDP;m_call.OnInitEnd(m_vDP.back(), m_vDpUpper.back());//预处理增加的一位for (int i = iEleCount - 1;i > 0;i--) {m_call.OnEnum(i,m_vDpUpper[i], m_vDpUpper[i + 1], pHigh[i],m_iCustomStatusCount);for (auto j = m_minEle; j < pHigh[i];j++) {m_call.OnEnum(i,m_vDpUpper[i], m_vDP[i + 1], j, m_iCustomStatusCount);}for (auto j = m_minEle; j <= m_maxEle;j++) {m_call.OnEnum(i,m_vDP[i], m_vDP[i + 1], j, m_iCustomStatusCount);}}m_call.OnEnum(0,m_vDpUpper[0], m_vDpUpper[1], pHigh[0], m_iCustomStatusCount);for (auto j = m_minEle; j < pHigh[0];j++) {m_call.OnEnum(0,m_vDP[0], m_vDP[1], j, m_iCustomStatusCount);}}ResultType Sum(int iMinCustomStatu, int iMaxCustomStatu) {ResultType ret = 0;for (int i = iMinCustomStatu; i <= iMaxCustomStatu;i++) {ret += m_vDP[0][i] + m_vDpUpper[0][i];}return ret;}ResultType Sum() {return Sum(0, m_iCustomStatusCount - 1);}vector<vector<ResultType>> m_vDP, m_vDpUpper;const ELE m_minEle, m_maxEle;
protected:const int m_iCustomStatusCount;IUpperDPCall<ELE, ResultType>& m_call;
};template<class ELE, class ResultType>class CCall :public IUpperDPCall<ELE, ResultType>{public:CCall(const string& str):m_str(str){} virtual void OnInitEnd(vector<ResultType>& dp, vector<ResultType>& upDp) {dp[0] = upDp[0] = 1;}virtual void OnEnum(int n, vector<ResultType>& dp, const vector<ResultType>& vNext, ELE curValue, int iCustomStatusCount){int a = curValue - '0';for (int carryCur = 0;carryCur < 2;carryCur++) {for (int m = 0;m < 8;m++) {const bool carryNext = m & 4;int b = (carryCur ? 10 : 0) + m_str[n] - '0' - a - carryNext;if ((b < 0)||(b>9)) { continue; }if ((m & 2) && a) { continue; }if ((m & 1) && b) { continue; }const int m1 = 4 * carryCur + 2 * (0 == a) + (0 == b);dp[m1] += vNext[m];}}}string m_str;};class Solution {public:long long countNoZeroPairs(long long n) {string s = to_string(n);CCall<char, long long> call(s);CUperrDP<char, long long> dp(8, call, '0', '9');dp.Init(s.c_str(), s.length());long long ans = dp.Sum(0, 3) ;if (-1 == s.find("0")) { ans -= 2; }return ans ;}};
单元测试
TEST_METHOD(TestMethod11){auto res = Solution().countNoZeroPairs(2);AssertEx(1LL, res);}TEST_METHOD(TestMethod12){auto res = Solution().countNoZeroPairs(3);AssertEx(2LL, res);}TEST_METHOD(TestMethod13){auto res = Solution().countNoZeroPairs(11);AssertEx(8LL, res);}TEST_METHOD(TestMethod14){auto res = Solution().countNoZeroPairs(10);AssertEx(9LL, res);}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。