【动态规划】P11188 「KDOI-10」商店砍价|普及+

【动态规划】P11188 「KDOI-10」商店砍价|普及+

本文涉及知识点

C++动态规划

P11188 「KDOI-10」商店砍价

题目背景

English Statement. You must submit your code at the Chinese version of the statement.

您可以点击 这里 下载本场比赛的选手文件。

You can click here to download all tasks and examples of the contest.

密码 / Password:rAnHoUyaSuoBaoMimaNijuEdefAngsHa2)2$1)0(2@0!

本场比赛所有题目从标准输入读入数据,输出到标准输出。

题目描述

有一个正整数 n n n,保证其只由数字 1 ∼ 9 1\sim 9 1∼9 构成。

你可以做任意多次如下操作:

  • 选择 n n n 的一个数位 x x x,花费 v x v_x vx​ 的代价删除它,注意,此时 n n n 的数位个数会减少 1 1 1, n n n 的值也会发生相应的变化;
  • 或者,花费 n n n 的代价把剩余的所有数位删除。

求把整个数删除的最小代价。

输入格式

从标准输入读入数据。

本题有多组测试数据。

输入的第一行包含一个正整数 c c c,表示测试点编号。 c = 0 c=0 c=0 表示该测试点为样例。

第二行包含一个正整数 t t t,表示测试数据组数。

对于每组测试数据:

  • 第一行一个正整数 n n n,表示这个数的初始值。
  • 第二行九个正整数 v 1 , v 2 , … , v 9 v_1,v_2,\dots,v_9 v1​,v2​,…,v9​,表示删除每个数位的代价。

输出格式

输出到标准输出。

对于每组测试数据:

  • 输出一行一个正整数,表示最小代价。

输入输出样例 #1

输入 #1

0 3 123 10 10 10 10 10 10 10 10 10 1121 2 1 2 2 2 2 2 2 2 987654321 1 2 3 4 5 6 7 8 9 

输出 #1

21 6 45 

说明/提示

【样例 1 解释】

对于第一组测试数据,最优操作方案如下:

  • 删除数位 2 2 2,代价为 10 10 10,此时 n n n 变为 13 13 13;
  • 删除数位 3 3 3,代价为 10 10 10,此时 n n n 变为 1 1 1;
  • 删除 n n n 的剩余所有数位,代价为 1 1 1。

总代价为 10 + 10 + 1 = 21 10+10+1=21 10+10+1=21,可以证明,这是代价的最小值。

对于第二组测试数据,一种最优操作方案如下:

  • 删除第一个数位 1 1 1,代价为 2 2 2,此时 n n n 变为 121 121 121;
  • 删除最后一个数位 1 1 1,代价为 2 2 2,此时 n n n 变为 12 12 12;
  • 删除数位 2 2 2,代价为 1 1 1,此时 n n n 变为 1 1 1;
  • 删除 n n n 的剩余所有数位,代价为 1 1 1。

总代价为 2 + 2 + 1 + 1 = 6 2+2+1+1=6 2+2+1+1=6。

【样例 2】

见选手目录下的 bargain/bargain2.inbargain/bargain2.ans

这个样例满足测试点 3 ∼ 6 3\sim 6 3∼6 的约束条件。

【样例 3】

见选手目录下的 bargain/bargain3.inbargain/bargain3.ans

这个样例满足测试点 11 11 11 的约束条件。

【样例 4】

见选手目录下的 bargain/bargain4.inbargain/bargain4.ans

这个样例满足测试点 17 , 18 17,18 17,18 的约束条件。

【样例 5】

见选手目录下的 bargain/bargain5.inbargain/bargain5.ans

这个样例满足测试点 23 ∼ 25 23\sim 25 23∼25 的约束条件。


【数据范围】

对于全部的测试数据,保证:

  • 1 ≤ t ≤ 10 1\le t\le 10 1≤t≤10;
  • 1 ≤ n < 1 0 1 0 5 1\le n< 10^{10^5} 1≤n<10105;
  • 对于任意 1 ≤ i ≤ 9 1\le i\le 9 1≤i≤9, 1 ≤ v i ≤ 1 0 5 1\le v_i\le 10^5 1≤vi​≤105;
  • n n n 由数字 1 ∼ 9 1\sim 9 1∼9 构成。
测试点 n < n< n< v i ≤ v_i\le vi特殊性质
1 1 1 100 100 100 1 0 5 10^5 105
2 2 2 1 0 3 10^3 103 1 0 5 10^5 105
3 ∼ 6 3\sim 6 36 1 0 18 10^{18} 1018 1 0 5 10^5 105
7 ∼ 9 7\sim 9 79 1 0 40 10^{40} 1040 1 0 5 10^5 105
10 10 10 1 0 1 0 5 10^{10^5} 10105 1 0 5 10^5 105 n n n 由至多一种数字构成
11 11 11 1 0 1 0 5 10^{10^5} 10105 1 0 5 10^5 105 n n n 由至多两种数字构成
12 , 13 12,13 12,13 1 0 1 0 5 10^{10^5} 10105 1 0 5 10^5 105 n n n 由至多三种数字构成
14 ∼ 16 14\sim 16 1416 1 0 1 0 3 10^{10^3} 10103 1 0 5 10^5 105 v 1 = v 2 = v 3 = ⋯ = v 9 v_1=v_2=v_3=\dots =v_9 v1=v2=v3==v9
17 , 18 17,18 17,18 1 0 1 0 5 10^{10^5} 10105 1 0 5 10^5 105 v 1 = v 2 = v 3 = ⋯ = v 9 v_1=v_2=v_3=\dots =v_9 v1=v2=v3==v9
19 , 20 19,20 19,20 1 0 100 10^{100} 10100 100 100 100
21 , 22 21,22 21,22 1 0 1 0 3 10^{10^3} 10103 1 0 3 10^3 103
23 ∼ 25 23\sim 25 2325 1 0 1 0 5 10^{10^5} 10105 1 0 5 10^5 105

动态规划

性质一:最后删除前,数位一定不超过5位。如果超过5位,直接删除的成本是:
x × 1 0 i − 1 + y x\times 10^{i-1}+y x×10i−1+y,i是位数,x是最高位。删除最高位,再删除的成本是 v x + y v_x+y vx​+y,本题 v x ≤ 1 0 5 v_x \le10^5 vx​≤105,故i > 5时,删除最高位再删除时不劣解。
性质二:最后删除x,相当于节约 x 各位的成本 − x x各位的成本-x x各位的成本−x,删除所有位的成本- max ⁡ ( 节约的成本 ) \max(节约的成本) max(节约的成本)便是答案。

动态规划的状态表示

dp[n][j]表示处理完s的后n位,保留了j位节约的最大成本。 n ∈ [ 0 , N ] , j ∈ [ 0 , 5 ] n \in[0,N],j\in[0,5] n∈[0,N],j∈[0,5]。
空间复杂度: O(n)

动态规划的填表顺序

枚举前驱状态,和操作。n从0到N-1,j任意。

动态规划的转移方程

如果倒数第n各位数z不保留
dp[n+1][j] = dp[n][j]
如果保留:
d p [ n + 1 ] [ j + 1 ] = d p [ n ] [ j ] + z ∗ 1 0 j − v z dp[n+1][j+1] = dp[n][j] + z*10^j - v_z dp[n+1][j+1]=dp[n][j]+z∗10j−vz​

动态规划的初始值

dp[0][0] ,其它LLONG_MIN/2。

动态规划的范围值

sum - max(dp.back()) 。sum是直接删除所有位的成本和。

代码

核心代码

#include<iostream>#include<sstream>#include<vector>#include<map>#include<unordered_map>#include<set>#include<unordered_set>#include<string>#include<algorithm>#include<functional>#include<queue>#include<stack>#include<iomanip>#include<numeric>#include<math.h>#include<climits>#include<assert.h>#include<cstring>#include<list>#include<array>#include<bitset>#include<chrono>usingnamespace std::chrono;usingnamespace std;template<classT1,classT2> std::istream&operator>>(std::istream& in, pair<T1, T2>& pr){ in >> pr.first >> pr.second;return in;}template<classT1,classT2,classT3> std::istream&operator>>(std::istream& in, tuple<T1, T2, T3>& t){ in >> get<0>(t)>> get<1>(t)>> get<2>(t);return in;}template<classT1,classT2,classT3,classT4> std::istream&operator>>(std::istream& in, tuple<T1, T2, T3, T4>& t){ in >> get<0>(t)>> get<1>(t)>> get<2>(t)>> get<3>(t);return in;}template<classT1,classT2,classT3,classT4,classT5,classT6,classT7> std::istream&operator>>(std::istream& in, tuple<T1, T2, T3, T4, T5, T6, T7>& t){ in >> get<0>(t)>> get<1>(t)>> get<2>(t)>> get<3>(t)>> get<4>(t)>> get<5>(t)>> get<6>(t);return in;}template<classT=int> vector<T>Read(){int n; cin >> n; vector<T>ret(n);for(int i =0; i < n; i++){ cin >> ret[i];}return ret;}template<classT=int> vector<T>ReadNotNum(){ vector<T> ret; T tmp;while(cin >> tmp){ ret.emplace_back(tmp);if('\n'== cin.get()){break;}}return ret;}template<classT=int> vector<T>Read(int n){ vector<T>ret(n);for(int i =0; i < n; i++){ cin >> ret[i];}return ret;}template<int N =1'000'000>classCOutBuff{public:COutBuff(){ m_p = puffer;}template<classT>voidwrite(T x){int num[28], sp =0;if(x <0)*m_p++='-', x =-x;if(!x)*m_p++=48;while(x) num[++sp]= x %10, x /=10;while(sp)*m_p++= num[sp--]+48;AuotToFile();}voidwritestr(constchar* sz){strcpy(m_p, sz); m_p +=strlen(sz);AuotToFile();}inlinevoidwrite(char ch){*m_p++= ch;AuotToFile();}inlinevoidToFile(){fwrite(puffer,1, m_p - puffer,stdout); m_p = puffer;}~COutBuff(){ToFile();}private:inlinevoidAuotToFile(){if(m_p - puffer > N -100){ToFile();}}char puffer[N],* m_p;};template<int N =1'000'000>classCInBuff{public:inlineCInBuff(){}inline CInBuff<N>&operator>>(char& ch){FileToBuf();while(('\r'==*S)||('\n'==*S)||(' '==*S)){ S++;}//忽略空格和回车 ch =*S++;return*this;}inline CInBuff<N>&operator>>(int& val){FileToBuf();intx(0),f(0);while(!isdigit(*S)) f |=(*S++=='-');while(isdigit(*S)) x =(x <<1)+(x <<3)+(*S++^48); val = f ?-x : x; S++;//忽略空格换行 return*this;}inline CInBuff&operator>>(longlong& val){FileToBuf();longlongx(0);intf(0);while(!isdigit(*S)) f |=(*S++=='-');while(isdigit(*S)) x =(x <<1)+(x <<3)+(*S++^48); val = f ?-x : x; S++;//忽略空格换行return*this;}template<classT1,classT2>inline CInBuff&operator>>(pair<T1, T2>& val){*this>> val.first >> val.second;return*this;}template<classT1,classT2,classT3>inline CInBuff&operator>>(tuple<T1, T2, T3>& val){*this>> get<0>(val)>> get<1>(val)>> get<2>(val);return*this;}template<classT1,classT2,classT3,classT4>inline CInBuff&operator>>(tuple<T1, T2, T3, T4>& val){*this>> get<0>(val)>> get<1>(val)>> get<2>(val)>> get<3>(val);return*this;}template<classT=int>inline CInBuff&operator>>(vector<T>& val){int n;*this>> n; val.resize(n);for(int i =0; i < n; i++){*this>> val[i];}return*this;}template<classT=int> vector<T>Read(int n){ vector<T>ret(n);for(int i =0; i < n; i++){*this>> ret[i];}return ret;}template<classT=int> vector<T>Read(){ vector<T> ret;*this>> ret;return ret;}private:inlinevoidFileToBuf(){constint canRead = m_iWritePos -(S - buffer);if(canRead >=100){return;}if(m_bFinish){return;}for(int i =0; i < canRead; i++){ buffer[i]= S[i];//memcpy出错 } m_iWritePos = canRead; buffer[m_iWritePos]=0; S = buffer;int readCnt =fread(buffer + m_iWritePos,1, N - m_iWritePos,stdin);if(readCnt <=0){ m_bFinish =true;return;} m_iWritePos += readCnt; buffer[m_iWritePos]=0; S = buffer;}int m_iWritePos =0;bool m_bFinish =false;char buffer[N +10],* S = buffer;};classSolution{public:longlongAns(const string& s, vector<int>& v){constint N = s.length(); vector<longlong>pre(6, LLONG_MIN /2); pre[0]=0; vector<int> p10 ={1,10,100,1000,10'000,100'000};for(int n =0; n < N; n++){constint z = s[N -1- n]-'0';auto cur = pre;//不保留for(int j =0; j +1<6; j++){ cur[j +1]=max(cur[j +1], v[z -1]- p10[j]* z + pre[j]);} pre.swap(cur);}longlong sum =0;for(constauto& ch : s){ sum += v[ch -'1'];}return sum -*max_element(pre.begin(), pre.end());}};intmain(){#ifdef_DEBUGfreopen("a.in","r",stdin);#endif// DEBUG  ios::sync_with_stdio(0); cin.tie(nullptr);//CInBuff<> in; COutBuff<10'000'000> ob;int c, t; cin >> c >> t; string s;for(int i =0; i < t; i++){ cin >> s;auto v = Read<int>(9);#ifdef_DEBUG //printf("M=%d", M);Out(s,",s=");Out(v,",v=");//Out(ss, ",ss=");//Out(ope, ",ope=");#endif// DEBUG auto res =Solution().Ans(s,v); cout << res <<"\n";}return0;}

单元测试

TEST_METHOD(TestMlethod11){auto res =Solution().Ans("9", vector<int>{1,1,1,1,1,1,1,1,1});AssertEx(1LL, res);}TEST_METHOD(TestMlethod12){ s ="123", v ={10,10,10,10,10,10,10,10,10};auto res =Solution().Ans(s, v);AssertEx(21LL, res);}TEST_METHOD(TestMlethod13){ s ="1121", v ={2,1,2,2,2,2,2,2,2};auto res =Solution().Ans(s, v);AssertEx(6LL, res);}TEST_METHOD(TestMlethod14){ s ="987654321", v ={1,2,3,4,5,6,7,8,9};auto res =Solution().Ans(s, v);AssertEx(45LL, res);}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步ZEEKLOG学院,听白银讲师(也就是鄙人)的讲解。
https://edu.ZEEKLOG.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.ZEEKLOG.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

Read more

Material Files:Android上最优雅的开源文件管理器终极指南 [特殊字符]️

Material Files:Android上最优雅的开源文件管理器终极指南 🗂️ 【免费下载链接】MaterialFilesMaterial Design file manager for Android 项目地址: https://gitcode.com/gh_mirrors/ma/MaterialFiles Material Files是一款专为Android设计的Material Design风格文件管理器,它不仅界面美观,而且功能强大,完全免费开源!无论你是新手还是资深用户,这款应用都能为你提供流畅的文件管理体验。✨ 为什么选择Material Files?🤔 极致美观的用户界面 Material Files采用了Google Material Design设计语言,整个界面简洁大方,色彩搭配和谐。无论是浅色主题还是深色主题,都能给你带来愉悦的视觉体验。 完全开源安全可靠 作为开源项目,Material Files的代码完全公开透明,你可以放心使用而不用担心隐私问题。项目的所有功能都是免费的,没有任何隐藏费用! 快速安装步骤 📱 方法一:通过G

By Ne0inhk
Linux系统学习【深入剖析Git的原理和使用(上)】

Linux系统学习【深入剖析Git的原理和使用(上)】

🔥承渊政道:个人主页 ❄️个人专栏: 《C语言基础语法知识》《数据结构与算法》 《C++知识内容》《Linux系统知识》 ✨逆境不吐心中苦,顺境不忘来时路!🎬 博主简介: 引言:在软件开发的全流程中,版本控制是保障协作效率、规避开发风险的核心基石,而Git作为目前最流行、最强大的分布式版本控制系统,早已渗透到从个人开发到大型企业级项目的每一个环节.无论是多人协作时的代码冲突解决、开发过程中的版本回溯,还是跨环境的代码同步、分支管理,Git都以其高效、安全、灵活的特性,成为开发者必备的核心工具.然而,多数开发者对Git的使用仍停留在“会用基础命令”的层面——知道用git add提交暂存、git commit提交本地、git push推送远程,却未必理解这些命令背后的底层逻辑:暂存区(Stage)、本地仓库(Local Repository)、远程仓库(Remote Repository)之间的数据流是怎样的?Git如何高效追踪文件的每一次变更?分布式架构与SVN等集中式版本控制系统相比,核心优势到底体现在哪里? 基于此,

By Ne0inhk

GitHub汉化插件完整指南:打造个性化中文开发环境

GitHub汉化插件完整指南:打造个性化中文开发环境 【免费下载链接】github-chineseGitHub 汉化插件,GitHub 中文化界面。 (GitHub Translation To Chinese) 项目地址: https://gitcode.com/gh_mirrors/gi/github-chinese 还在为GitHub的英文界面感到困扰吗?GitHub汉化插件能够将整个平台界面完美转换为中文环境,让技术学习和项目管理变得更加轻松自然。这款开源工具不仅支持完整的界面中文化,还提供亮色与暗色主题的完美适配,为你打造个性化的开发体验。 🚀 快速上手安装步骤 第一步:安装脚本管理器 这是运行汉化插件的基础环境,推荐选择: * Tampermonkey:功能丰富,社区活跃 * Violentmonkey:开源轻量,隐私友好 安装方法: 1. 打开浏览器应用商店 2. 搜索对应名称并点击安装 3. 确认工具栏出现相应图标 安全提示:务必从官方渠道下载,避免使用第三方来源的安装包。 第二步:获取汉化脚本 有两种方式可以获取最新的汉化脚本:

By Ne0inhk

图表数据提取神器:WebPlotDigitizer 快速上手全攻略

图表数据提取神器:WebPlotDigitizer 快速上手全攻略 【免费下载链接】WebPlotDigitizerComputer vision assisted tool to extract numerical data from plot images. 项目地址: https://gitcode.com/gh_mirrors/web/WebPlotDigitizer 还在为从图表图片中提取数据而烦恼吗?WebPlotDigitizer 这款计算机视觉辅助工具能够帮你快速从各种图表图像中提取精确的数值数据。无论你是科研人员需要从论文图表获取实验数据,还是工程师要从技术报告提取趋势曲线,这个工具都能在几分钟内完成数据转换。 新手必备:快速搭建你的数据提取环境 在开始使用之前,你需要确保系统环境准备就绪。首先检查 Node.js 版本,建议使用 v14 或更高版本: node -v npm -v 如果未安装,Ubuntu 用户可以通过以下命令快速安装: sudo apt update

By Ne0inhk