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

 

转载于:https://www.cnblogs.com/gromah/p/4427996.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值