牌堆
牌堆
有 $n$ 张纸牌,编号 $1 \sim n$。
其中,第 $i$ 张纸牌的价值为 $v_i$。
你要玩一个游戏,具体流程如下:
首先,你要将 $n$ 张牌一张叠一张地堆成一个牌堆,牌堆中纸牌的具体排列顺序由你决定。
接下来,你需要依次进行 $m$ 次操作。
关于第 $i$ 次操作:
- 你的任务是将编号为 $a_i$ 的纸牌从牌堆中抽出,并置于牌堆顶部。
- 如果执行此次操作时,不存在纸牌位于纸牌 $a_i$ 之上,即纸牌 $a_i$ 本来就位于牌堆顶部,则此次操作无需付出任何代价。
- 如果执行此次操作时,存在纸牌位于纸牌 $a_i$ 之上,则此次操作需要付出代价,具体代价为所有位于纸牌 $a_i$ 之上的纸牌的价值之和(不要将价值和编号混淆)。
请你合理安排初始时牌堆中的纸牌排列顺序,从而使得执行完所有操作需要付出的总代价尽可能小。
输出总代价的最小可能值。
例如,如果一共有 $3$ 张纸牌,价值分别为 $1,2,3$,一共需要进行 $5$ 次操作,每次操作需要置于牌堆顶部的卡牌分别为 $1,3,2,3,1$。
那么一种最佳的初始时牌堆中纸牌排列顺序为从上到下依次为纸牌 $1,3,2$。
具体操作过程如下:
- 第 $1$ 次操作,需要将纸牌 $1$ 置于牌堆顶部,由于纸牌 $1$ 本来就在牌堆顶部,因此,所需代价为 $0$,执行完此次操作后,牌堆中纸牌排列顺序仍为 $1,3,2$。
- 第 $2$ 次操作,需要将纸牌 $3$ 置于牌堆顶部,由于纸牌 $1$ 位于纸牌 $3$ 之上,因此,所需代价为 $v_1=1$,执行完此次操作后,牌堆中纸牌排列顺序为 $3,1,2$。
- 第 $3$ 次操作,需要将纸牌 $2$ 置于牌堆顶部,由于纸牌 $1,3$ 位于纸牌 $2$ 之上,因此,所需代价为 $v_1+v_3=4$,执行完此次操作后,牌堆中纸牌排列顺序为 $2,3,1$。
- 第 $4$ 次操作,需要将纸牌 $3$ 置于牌堆顶部,由于纸牌 $2$ 位于纸牌 $3$ 之上,因此,所需代价为 $v_2=2$,执行完此次操作后,牌堆中纸牌排列顺序为 $3,2,1$。
- 第 $5$ 次操作,需要将纸牌 $1$ 置于牌堆顶部,由于纸牌 $2,3$ 位于纸牌 $1$ 之上,因此,所需代价为 $v_2+v_3=5$,执行完此次操作后,牌堆中纸牌排列顺序为 $1,3,2$。
付出的总代价为 $0+1+4+2+5=12$。
输入格式
第一行包含两个整数 $n,m$。
第二行包含 $n$ 个整数 $v_1,v_2, \ldots ,v_n$。
第三行包含 $m$ 个整数 $a_1,a_2, \ldots ,a_m$。
输出格式
一个整数,表示总代价的最小可能值。
数据范围
前 $3$ 个测试点满足 $2 \leq n \leq 3$,$1 \leq m \leq 5$。
所有测试点满足 $2 \leq n \leq 500$,$1 \leq m \leq 1000$,$1 \leq v_i \leq 100$,$1 \leq a_i \leq n$。
输入样例:
3 5 1 2 3 1 3 2 3 1
输出样例:
12
解题思路
猜对了做法,来补一下证明。
先看第$1$次操作$a_1$,考虑$a_1$在初始卡牌中的位置。如果直接把$a_1$放在开头那么第$1$次操作就不需要执行,代价为$0$。否则把$a_1$放在后面的位置(除开头外的位置),那么代价就是前面所有牌的价值之和(大于$0$)。因此我们可以把$a_1$在初始卡牌中的位置分成两类,一类是放开头,另一类是不放开头。可以发现对于不放开头的情况,在操作完后$a_1$就变到了的开头的位置,这就变成了$a_1$放开头的情况。另外可以发现,不管是$a_1$放开头还是不放开头,在操作后其他牌的顺序都不变,即得到的结果都一样,而不放开头则会产生代价,因此必然选择第一种方案($a_1$放开头)最优。
接着考虑$a_2$,如果$a_2 = a_1$,由于本身就在开头因此代价为$0$。否则$a_2 \ne a_1$,一样的可以把$a_2$在初始卡牌中的位置分成两类:一类是放第$2$个位置($a_1$已经放在第$1$个位置了),另一类是放第$2$个位置之后的位置。用同样的分析方法可以发现选择第一种方案最优。
更一般的,定义$p$表示初始卡牌在经过每次操作后变化的结果。考虑$a_i$,如果$a_i$没有在之前出现过,那么$a_i$应该在初始卡牌中的第$k$个位置,其中$k$表示$1 \sim i-1$中出现过的牌的种类(因为每次操作后都要把牌移到开头,因此之前出现过的牌必然在$a_i$的前面)。要把$a_i$移动到开头,代价就是$\sum\limits_{j=1}^{k}{v_{p_j}}$。如果$a_i$在之前出现过,那么直接在$p$中找到$a_i$的位置$j$,累加前面的牌的价值就好了,代价就是$\sum\limits_{u=1}^{j-1}{v_{p_u}}$,然后再把$a_i$移到开头。
AC代码如下,时间复杂度为$O(nm)$:
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