牌堆

牌堆

有 $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/

posted @ 2023-04-23 21:19  onlyblues  阅读(26)  评论(0编辑  收藏  举报
Web Analytics