BZOJ 3971 Матрёшка 解题报告

很自然想到区间 DP。

设 $Dp[i][j]$ 表示把区间 $[i, j]$ 内的套娃合并成一个所需要的代价,那么有:

  • $Dp[i][i] = 0$
  • $Dp[i][j] = min\{Dp[i][k] + Dp[k + 1][j] + Merge([i, k], [k + 1, j])\} (i \le k < j)$

于是问题在于算 $Merge([a, b], [c, d])$。

我们考虑一下:区间 $[a, b]$ 内的哪些套娃是需要打开的:

是不是 $[a, b]$ 中所有大于 $[c, d]$ 中最小的套娃都需要打开,来装 $[c, d]$ 中最小的套娃呢?

$[c, d]$ 同理。

于是我们可以预处理 $Sum[i][x]$ 为在前 $i$ 个套娃中,大小 $\le x$ 的套娃的个数,

那么就可以 $O(1)$ 地算 $Merge([a, b], [c, d])$ 了。

有一个问题,如果在 $[a, b]$、$[c, d]$ 中有相同大小的套娃怎么办?

不着急,先往下看。

处理完 $Dp[i][j]$ 后,我们再设立一个 $F[i]$ 表示把前 $i$ 个套娃装成若干个完好的套娃集所需代价的最小值,那么就有:

  • $F[i] = min(F[j] + Dp[j + 1][i]) (mex([j + 1, i]) = i - j + 1)$
  • $mex([u, v]) = min(x) (x\notin \{A_u, A_{u+1}, \dots, A_v\})$

当然,如果不能找到合法的转移点,那么令 $F[i] = INF$。

然后对于一个合法的方案,不可能出现相同大小的套娃被装进同一个套娃内的情况,

所以 $[a, b]$、$[c, d]$ 中有相同大小的套娃的话,那么这个区间一定不合法,所以我们大可不必考虑那么多了。

于是最后看 $F[n]$ 就可以了。令 $M = max\{A_i\}$

时间复杂度 $O(n^3 + n^2M)$,空间复杂度 $O(n^2 + nM)$。

 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <iostream>
 5 #include <algorithm>
 6 using namespace std;
 7 #define N 500 + 5
 8 #define INF 593119681
 9
10 int n, Max;
11 int A[N], _Dp[N], T[N];
12 int Sum[N][N], Dp[N][N], Mex[N][N], Min[N][N];
13
14 inline void Prepare()
15 {
16     for (int i = 1; i <= n; i ++)
17         Sum[i][A[i]] ++;
18     for (int i = 1; i <= n; i ++)
19         for (int j = 1; j <= Max; j ++)
20             Sum[i][j] += Sum[i][j - 1] + Sum[i - 1][j] - Sum[i - 1][j - 1];
21
22     for (int i = 1; i <= n; i ++)
23     {
24         for (int j = 1; j <= Max; T[j ++] = 0) ;
25         for (int j = i; j <= n; j ++)
26         {
27             T[A[j]] ++;
28             for (Mex[i][j] = 1; T[Mex[i][j]]; Mex[i][j] ++) ;
29             Min[i][j] = i == j ? A[i] : min(Min[i][j - 1], A[j]);
30         }
31     }
32 }
33
34 inline int Calc(int l, int mid, int r)
35 {
36     int min_1 = Min[l][mid], res = Sum[r][Max] - Sum[mid][Max] - Sum[r][min_1] + Sum[mid][min_1];
37     min_1 = Min[mid + 1][r], res += Sum[mid][Max] - Sum[l - 1][Max] - Sum[mid][min_1] + Sum[l - 1][min_1];
38     return res;
39 }
40
41 int main()
42 {
43     #ifndef ONLINE_JUDGE
44         freopen("3971.in", "r", stdin);
45         freopen("3971.out", "w", stdout);
46     #endif
47
48     scanf("%d", &n);
49     for (int i = 1; i <= n; i ++)
50     {
51         scanf("%d", A + i);
52         Max = max(Max, A[i]);
53     }
54     Prepare();
55     for (int len = 0; len < n; len ++)
56         for (int s = 1; s + len <= n; s ++)
57         {
58             int i = s, j = s + len;
59             if (i == j) Dp[i][j] = 0;
60             else
61             {
62                 Dp[i][j] = INF;
63                 for (int k = i; k < j; k ++)
64                     Dp[i][j] = min(Dp[i][j], Dp[i][k] + Dp[k + 1][j] + Calc(i, k, j));
65             }
66         }
67     for (int i = 1; i <= n; i ++) _Dp[i] = INF;
68     for (int i = 1; i <= n; i ++)
69         for (int j = 0; j < i; j ++)
70             if (Mex[j + 1][i] == i - j + 1)
71                 _Dp[i] = min(_Dp[i], _Dp[j] + Dp[j + 1][i]);
72     if (_Dp[n] >= INF) puts("Impossible");
73         else printf("%d\n", _Dp[n]);
74
75     #ifndef ONLINE_JUDGE
76         fclose(stdin);
77         fclose(stdout);
78     #endif
79     return 0;
80 }

3971_Gromah

时间: 2024-08-24 19:34:26

BZOJ 3971 Матрёшка 解题报告的相关文章

BZOJ 1044 木棍分割 解题报告(二分+DP)

来到机房刷了一道水(bian’tai)题.题目思想非常简单易懂(我的做法实际上参考了Evensgn 范学长,在此多谢范学长了) 题目摆上: 1044: [HAOI2008]木棍分割 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3162  Solved: 1182[Submit][Status][Discuss] Description 有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个

BZOJ 4341 [CF253 Printer] 解题报告

乍一看这个题好像可以二分优先度搞搞... 实际上能不能这么搞呢...? 我反正不会... 于是开始讲我的乱搞算法: 首先肯定要把任务按照优先度排序. 用一棵在线建点的线段树维护一个时刻是否在工作. 然后就依次插入任务,记为 i,具体而言就是二分其右端点,然后令这整个区间都变成 “工作” 的状态. 在 i 被插入之前,还要检验一下在当前情况那个神秘任务的右端点是不是题中所要求的那个. 如果是,并且 i-1 的优先度和 i 的优先度不相邻或者 i 就是最优先的任务,那么就令那个神秘任务的优先度为 i

BZOJ 3288 Mato矩阵 解题报告

这个题好神呀..Orz taorunz 有一个结论,这个结论感觉很优美: $$ans = \prod_{i=1}^{n}\varphi(i)$$ 至于为什么呢,大概是这样子的: 对于每个数字 $x$,第 $x$ 行有 $x - \varphi(x)$ 个数字不为 $1$,则说明这一行要被消 $x - \varphi(x)$ 次(别忘了每一行都会被 $1$ 给消一次),每次消元都会令 $A[x][x]$ 减一,所以 $A[x][x]$ 最后会变成 $\varphi(x)$,所以答案就是这个啦. 时

BZOJ 3983 Takeover Wars 解题报告

我猜了一个结论,能合并就合并,到了必须要敌对交易的时候才进行敌对交易. 然后合并的话,肯定是拿最大的两个去合并. 至于敌对交易,肯定是干掉对方最大的公司才是有意义的. 于是各种分类讨论...看代码好了... #include <cmath> #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> usi

BZOJ 4123 [Baltic2015] Hacker 解题报告

首先,Alice 会选择一个长度为 $\lfloor\frac{n+1}{2}\rfloor$ 的区间,我们把这个长度记为 $len$. 有这么一个结论:令 $F_i$ 为覆盖 $i$ 点的所有长度为 $len$ 的区间的元素和的最小值,那么答案就是 $F_i$ 的最大值. 因为 Bob 可以控制 Alice 最后选择的是什么区间. [扯淡 ing]大概是这样子: 假设 Alice 一开始选择的是红色的点 $i$,并且蓝色的线以左所覆盖的区间和就是 $F_i$,那么对于 Bob,他就可以选择绿色

BZOJ 3969 Low Power 解题报告

我们首先将所有电池排序,那么我们可以找到一组最优方案,使得一台机器的能量之差是相邻两电池的能量之差. 然后我们就二分这个答案,从前往后贪心地选这个数对,然后看是否所有的数对都是满足条件的. 假设这个数对是 i - 1, i,并且是第 j 个数对,那么我们称满足条件为: 2nk - i + 2 >= 2k(n - j + 1) 意思就是能拿出足够多的电池来组成机器人. 然后注意特判:如果不能选出足够多的数对就返回 false,我在这里 WA 到死... 毕竟 Gromah 太弱,只会做水题. 1

BZOJ 4036 [HAOI2015] Set 解题报告

首先我们不能一位一位的考虑,为什么呢? 你想想,你如果一位一位地考虑的话,那么最后就只有 $n$ 个数字,然而他给了你 $2^n$ 个数字,怎么看都不对劲呀.(我是因为这样子弄没过样例才明白的) 所以我们还是要想想其他的方法. 我们是要算步数的期望,然而步数是一个离散的整数,所以我们可以把问题转化一下: $$E(s) = \sum_{k=1}^{\infty}P(s\ge k)$$ 然后就好做了嘛. 我们可以求出一个 $F_i = \sum_{j\subseteq i} p_j$,表示随机选一个

BZOJ 3982 Stacking Plates 解题报告

我们首先可以得到:如果有一堆盘子里有一些相邻的盘子的直径相等,那么自然这些盘子可以统一处理,就可以缩成一个了. 然后我们接着考虑给每一堆盘子都染上一种颜色,那么操作的次数 step = diff * 2 - n + 1 其中 diff 表示最终的盘子堆中相邻的盘子的颜色不同的对数. 接着我们可以将盘子的直径离散化. 那么我们可以考虑Dp,设 Dp[s][i] 为处理完所有盘子直径小于等于 s 的盘子,并且最底下的盘子的颜色是 i 的 diff 的最小值. 至于转移的话呢,记直径为 s 的盘子个数

【BZOJ】3971 [WF2013]Матрёшка

[算法]区间DP [题解] 参考写法:BZOJ 3971 Матрёшка 解题报告 第二个DP可以预处理mex优化到O(nM+n2),不过我懒-- 第一个DP有另一种写法:不预处理,在一个n2取出来的的区间中,枚举决策点从左到右时,保留左最小值的可保留数不严格单调递增,保留右最小值的可保留数不严格单调递减,均摊O(1). ???[细节] f[0]=0初始化 inf+inf+inf就会爆int 区间第二重循环是i=1...n-p,否则有可能爆数组边界. #include<cstdio> #in