zoukankan      html  css  js  c++  java
  • GHOJ 1050 珠宝

    题目大意

      Elly有一排$n$颗珍珠($1 leq n leq 1000$)。每颗珍珠的颜色是$m$种不同的颜色的其中一种($1 leq m leq 15$),第$i$颗珍珠的颜色是$a[i]$。现在她想让颜色相同的珍珠要排在相邻,最终配置中颜色的顺序并不重要。必须移动的珍珠数量是多少才能按颜色分组?(移动珍珠意味着将它从行中取出然后将其插回任意位置——在任何两个珍珠之间或在所有珍珠之前或之后。)

    题解

      因为我们把珍珠取出来后就可以插入任意位置,所以我们可以排好后再插入,也就是我们并不需要管取出后的珍珠,只需要考虑取出哪些珍珠。

      我们设$dp[i][S]$表示前$i$颗珍珠中,所有珍珠的颜色都在集合$S$内的最少需要取出的珍珠数量。

      容易想到。当$a[i] otin S$时,有

      $$dp[i][S] = dp[i - 1][S] + 1$$

      而当$a[i] in S$时,此时第$i$颗珍珠要么保留,要么也要取出。

      如果取出的话,那还是之前的状态转移方程

      $$dp[i][S] = dp[i - 1][S] + 1$$

      如果保留的话,那我们要枚举$j in [1,i]$,在$[1,j)$的范围内集合为$S - S_{a[i]}$的情况,这里已经用$dp[j - 1][S - S_{a[i]}]$记录了;在$[j,i]$的范围内集合仍为$S$。

      当$a[i] ot = a[j]$时,显然第$j$颗珍珠要取出。这里我们用$Count(j,i)$,表示$[j,i]$中要取出的珍珠数量。

      当$a[i] = a[j]$时,则有状态转移方程

      $$dp[i][S] = dp[j - 1][S - S_{a[i]}] + Count(j, i)$$

      综合两种情况,可以得到最终的状态转移方程为

      $$dp[i][S] = egin{cases} dp[i - 1][S] + 1 &a[i] otin S \ min {dp[i - 1][S] + 1, underset{1 leq j leq i}{min} { dp[j - 1][S - S_{a[i]}] + Count(j, i) } } &a[i] in S end{cases}$$

      此时的时间复杂度为$O(2^{m} n^{2})$,还是TLE。

      实际上我们只需要记录有关$j$部分的前缀最小值,就能省去枚举$j$的循环,时间复杂度降为$O(2^{m}n)$,具体细节看下面的代码。

    #include <iostream>
    #include <cstring>
    
    #define MAX_N (50 + 5)
    #define MAX_M (15 + 5)
    #define MAX_S ((1 << 15) + 5) 
    
    using namespace std;
    
    int n, m;
    int a[MAX_N];
    int sum[MAX_M][MAX_N];
    int dp[MAX_N][MAX_S], mdp[MAX_M][MAX_S], l[MAX_M][MAX_S];
    
    int main()
    {
        cin >> n >> m;
        for(int i = 1; i <= n; ++i)
        {
            cin >> a[i];
        }
        memset(dp, 0x3f, sizeof dp);
        memset(mdp, 0x3f, sizeof mdp);
        const int lim = (1 << m) - 1;
        for(int c = 1; c <= m; ++c)
        {
            for(int i = 1; i <= n; ++i)
            {
                sum[c][i] = sum[c][i - 1];
                if(a[i] != c) ++sum[c][i];
            }
        }
        for(int s = 0; s <= lim; ++s)
        {
            dp[0][s] = 0;
        }
        for(int i = 1; i <= n; ++i)
        {
            dp[i][0] = i;
        }
        int p;
        for(int s = 1; s <= lim; ++s)
        {
            for(int i = 1; i <= n; ++i)
            {
                p = 1 << a[i] - 1; 
                dp[i][s] = dp[i - 1][s] + 1;
                if(s & p)
                {
                    mdp[a[i]][s] = min(mdp[a[i]][s] + sum[a[i]][i] - sum[a[i]][l[a[i]][s]], dp[i - 1][s - p]);
                    dp[i][s] = min(dp[i][s], mdp[a[i]][s]);
                    l[a[i]][s] = i;
                }
            }
        }
        cout << dp[n][lim]; 
        return 0;
    }
    参考程序

       毒瘤区赛,毒瘤出题人

  • 相关阅读:
    小师妹学JVM之:JIT中的PrintCompilation
    八张图彻底了解JDK8 GC调优秘籍-附PDF下载
    区块链系列教程之:比特币中的网络和区块链
    从印度兵力分布聊聊Mybatis中#和$的区别
    区块链系列教程之:比特币的钱包与交易
    小师妹学JVM之:JIT中的LogCompilation
    从发布-订阅模式谈谈 Flask 的 Signals
    基于 Ajax 请求的 Flask-Login 认证
    解决 openpyxl 垂直分页符和水平分页符同时添加的问题
    用 python 抓取知乎指定回答下的视频
  • 原文地址:https://www.cnblogs.com/kcn999/p/11276808.html
Copyright © 2011-2022 走看看