牌堆
牌堆
有 张纸牌,编号 。
其中,第 张纸牌的价值为 。
你要玩一个游戏,具体流程如下:
首先,你要将 张牌一张叠一张地堆成一个牌堆,牌堆中纸牌的具体排列顺序由你决定。
接下来,你需要依次进行 次操作。
关于第 次操作:
- 你的任务是将编号为 的纸牌从牌堆中抽出,并置于牌堆顶部。
- 如果执行此次操作时,不存在纸牌位于纸牌 之上,即纸牌 本来就位于牌堆顶部,则此次操作无需付出任何代价。
- 如果执行此次操作时,存在纸牌位于纸牌 之上,则此次操作需要付出代价,具体代价为所有位于纸牌 之上的纸牌的价值之和(不要将价值和编号混淆)。
请你合理安排初始时牌堆中的纸牌排列顺序,从而使得执行完所有操作需要付出的总代价尽可能小。
输出总代价的最小可能值。
例如,如果一共有 张纸牌,价值分别为 ,一共需要进行 次操作,每次操作需要置于牌堆顶部的卡牌分别为 。
那么一种最佳的初始时牌堆中纸牌排列顺序为从上到下依次为纸牌 。
具体操作过程如下:
- 第 次操作,需要将纸牌 置于牌堆顶部,由于纸牌 本来就在牌堆顶部,因此,所需代价为 ,执行完此次操作后,牌堆中纸牌排列顺序仍为 。
- 第 次操作,需要将纸牌 置于牌堆顶部,由于纸牌 位于纸牌 之上,因此,所需代价为 ,执行完此次操作后,牌堆中纸牌排列顺序为 。
- 第 次操作,需要将纸牌 置于牌堆顶部,由于纸牌 位于纸牌 之上,因此,所需代价为 ,执行完此次操作后,牌堆中纸牌排列顺序为 。
- 第 次操作,需要将纸牌 置于牌堆顶部,由于纸牌 位于纸牌 之上,因此,所需代价为 ,执行完此次操作后,牌堆中纸牌排列顺序为 。
- 第 次操作,需要将纸牌 置于牌堆顶部,由于纸牌 位于纸牌 之上,因此,所需代价为 ,执行完此次操作后,牌堆中纸牌排列顺序为 。
付出的总代价为 。
输入格式
第一行包含两个整数 。
第二行包含 个整数 。
第三行包含 个整数 。
输出格式
一个整数,表示总代价的最小可能值。
数据范围
前 个测试点满足 ,。
所有测试点满足 ,,,。
输入样例:
3 5 1 2 3 1 3 2 3 1
输出样例:
12
解题思路
猜对了做法,来补一下证明。
先看第次操作,考虑在初始卡牌中的位置。如果直接把放在开头那么第次操作就不需要执行,代价为。否则把放在后面的位置(除开头外的位置),那么代价就是前面所有牌的价值之和(大于)。因此我们可以把在初始卡牌中的位置分成两类,一类是放开头,另一类是不放开头。可以发现对于不放开头的情况,在操作完后就变到了的开头的位置,这就变成了放开头的情况。另外可以发现,不管是放开头还是不放开头,在操作后其他牌的顺序都不变,即得到的结果都一样,而不放开头则会产生代价,因此必然选择第一种方案(放开头)最优。
接着考虑,如果,由于本身就在开头因此代价为。否则,一样的可以把在初始卡牌中的位置分成两类:一类是放第个位置(已经放在第个位置了),另一类是放第个位置之后的位置。用同样的分析方法可以发现选择第一种方案最优。
更一般的,定义表示初始卡牌在经过每次操作后变化的结果。考虑,如果没有在之前出现过,那么应该在初始卡牌中的第个位置,其中表示中出现过的牌的种类(因为每次操作后都要把牌移到开头,因此之前出现过的牌必然在的前面)。要把移动到开头,代价就是。如果在之前出现过,那么直接在中找到的位置,累加前面的牌的价值就好了,代价就是,然后再把移到开头。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 510, M = 1010; 5 6 int v[N], a[M], p[N]; 7 bool vis[N]; 8 9 int main() { 10 int n, m; 11 scanf("%d %d", &n, &m); 12 for (int i = 1; i <= n; i++) { 13 scanf("%d", v + i); 14 } 15 for (int i = 1; i <= m; i++) { 16 scanf("%d", a + i); 17 } 18 int ret = 0; 19 for (int i = 1, k = 0; i <= m; i++) { 20 if (!vis[a[i]]) { // a[i]在之前没有出现过 21 vis[a[i]] = true; 22 for (int j = k; j; j--) { // 1~k都往后移一位,把a[i]放开头,同时累加前面牌的代价 23 p[j + 1] = p[j]; 24 ret += v[p[j]]; 25 } 26 p[1] = a[i]; 27 k++; // 多了一种牌 28 } 29 else { // a[i]在之前出现过 30 for (int j = 1; j <= k; j++) { 31 if (p[j] == a[i]) { // 找到a[i]的位置 32 for (int u = j - 1; u; u--) { // 1~j都往后移一位,把a[i]放开头,同时累加前面牌的代价 33 p[u + 1] = p[u]; 34 ret += v[p[u]]; 35 } 36 p[1] = a[i]; 37 break; 38 } 39 } 40 } 41 } 42 printf("%d", ret); 43 44 return 0; 45 }
参考资料
AcWing 4955. 牌堆(AcWing杯 - 周赛):https://www.acwing.com/video/4702/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17347789.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!