【动态规划】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

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
Claude Git 集成:代码协作与版本管理的实战指南

Claude Git 集成:代码协作与版本管理的实战指南

在 2026 年的 AI 开发浪潮中,Claude Code 已从单纯的代码生成工具,进化为能深度参与开发全流程的智能协作伙伴,而 Git 作为代码版本管理与团队协作的基石,二者的无缝集成,彻底重构了“AI 结对编程 + 版本管控”的高效工作模式。不同于传统 AI 工具仅能生成代码片段,Claude 与 Git 的集成的核心价值的是:让 AI 的每一次代码改动都可追溯、每一次协作都有规范、每一次版本迭代都更可控,既释放 AI 编程的效率优势,又守住版本管理的安全底线。 本文面向一线开发者与团队负责人,从基础认知、环境搭建、核心实操,到进阶技巧与问题排查,全程干货无冗余,带你从零掌握 Claude Git 集成的实战精髓,让 AI 协作与版本管理真正落地到日常开发中。 一、核心认知:为什么要做

By Ne0inhk

最近爆火的 OpenClaw Skills 合集开源了!已收录 700+!

在介绍这份令人眼花缭乱的“武器库”之前,先给还不了解 OpenClaw 的朋友补个课。 简单来说,OpenClaw 是目前 GitHub 上最火的本地化 AI Agent 平台(前身是 Clawd/Moltbot)。不同于只能在网页里陪聊的 ChatGPT,OpenClaw 是一个运行在你电脑终端里的“数字管家”。 * 本地优先:直接运行在你的 Mac/Linux/Windows 上,数据不出本地,拥有 Docker 沙箱级安全保护。 * 全渠道接入:你可以通过 WhatsApp、Telegram、Slack 甚至 iMessage 随时指挥它。 * 行动派:它不只是给你建议,而是能直接读写文件、运行命令、调用 API。 如果说 OpenClaw 是一个强悍的操作系统,那么下面要介绍的

By Ne0inhk
保姆级 GitHub 学生认证教程(零踩坑版)

保姆级 GitHub 学生认证教程(零踩坑版)

保姆级GitHub学生认证教程(零踩坑版) 全程手把手教学,重点标注避坑点,只要准备好材料,跟着走就能认证成功,亲测有效! 一、认证前提准备(缺一不可!) * GitHub账号:默认大家已拥有,无需额外注册(没有的话先注册一个,流程很简单)。 * 教育邮箱:必须是学校官方教育邮箱(结尾为@xxx.edu.cn),需向学校相关部门申请获取,无教育邮箱无法完成认证。 * 学信网在线认证报告:提前在学信网生成,后续需准备英文版(重点!)。 二、详细认证步骤(一步都别错!) 步骤1:修改GitHub个人资料(Profile) 1. 登录你的GitHub账号,点击页面右上角头像,在下拉菜单中选择【Settings】(设置); 2. 进入设置页面后,默认显示【Public Profile】(公开资料)页面,重点修改【Name】(姓名); 3.

By Ne0inhk