zoukankan      html  css  js  c++  java
  • [Luogu]P3694 邦邦的大合唱战队

    Before the Beginning

    转载请将本段放在文章开头显眼处,如有二次创作请标明。
    原文链接:https://www.codein.icu/lp3694/

    题意

    题目传送门

    (N) 个人,分别属于 (M) 个组,要求同组的人站在一起。
    每个人可以出队,出队后留出空位,出队后的人可以在任意空位归队。
    求最少出队人数。

    解法

    看到 (M) 的数据范围极小,猜测可能是状压DP。
    但没有想出来到底是如何状压,于是重新审视题意。

    全排列

    可以发现,最后同组的人站在一起,一定会形成组的顺序,例如:

    111111,222222,333333
    222222,111111,333333
    333333,111111,222222
    ....

    类似如此,那么可以全排列枚举组的顺序。
    而确定组的顺序后,每个组的范围就确定下来了。
    而对于每个人,如果他的位置恰好属于他组的范围,他便不用出队,否则他一定要出队。
    依次检验,取最小值,即可得到答案。
    复杂度为 (O(NM!)),只能通过四个点。

    考虑进行优化,可以发现这个 (O(N)) 能够用预处理前缀和的方式去掉。
    维护 (s_{i,j}) 代表前 (i) 人中属于 (j) 组的人数,即可用 (O(1)) 的时间计算出一组的范围内应有多少人出队。
    复杂度降低到 (O(M!)),可以通过七个点。

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    template<typename T>void read(T &r){static char c;r=0;for(c=getchar();c>'9'||c<'0';c=getchar());for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());}
    const int maxn = 1e5 + 10;
    const int maxm = 21;
    int n,m;
    int a[maxn];
    int num[maxm];
    int order[maxm];
    int s[maxn][maxm];
    int main()
    {
        read(n);
        read(m);
        for(int i = 1;i<=n;++i)
            read(a[i]),num[a[i]]++,s[i][a[i]]++;
        for(int i = 1;i<=n;++i)
            for(int j = 1;j<=m;++j)
                s[i][j] += s[i-1][j];
        for(int i = 1;i<=m;++i)
            order[i] = i;
        int ans = 1 << 30;
        do
        {
            int res = 0;
            int l = 0,r = 0;
            for(int i = 1;i<=m;++i)
            {
                l = r + 1;
                r = l + num[order[i]] - 1;
                res += num[i] - (s[r][order[i]] - s[l-1][order[i]]);
            }
            ans = min(ans,res);
        } while (next_permutation(order + 1, order + 1 + m));
        printf("%d",ans);
        return 0;
    }
    

    状压DP

    想要将 (O(M!)) 的复杂度降低到 (O(2^M)),使用状压DP的方法。
    有数据范围的提示,很容易定义出状态:
    状态 (S) 每位代表该组是否已处理完成。
    如何转移呢?可以枚举排在最后的组。
    计算出当前队列的长度,枚举排在最后的组,已知排在最后组的长度,即可确定排在最后组的范围。
    确定组范围后,即可计算出该范围内应有多少人出队。
    枚举当前状态每个可能的最后组,取最小值进行转移。

    dp[status] = min(dp[status],dp[status ^ (1<<(i-1))] + num[i] - (s[r][i] - s[l-1][i]));
    

    实质上,这个状压DP也是在枚举顺序。
    不同于全排列,状压转移时并不知道子状态中的顺序,只知道子状态已完成的组,而将当前组加在子状态队列最后。
    或许借此剪去了部分无用的枚举。

    笔者使用了记忆化搜索的写法,预处理了每个状态的队列长度。

    #include <cstdio>
    #include <cstring>
    using namespace std;
    template<typename T>
    inline T min(const T &a,const T &b){return a<b?a:b;}
    template<typename T>
    void read(T &r){static char c;r=0;for(c=getchar();c>'9'||c<'0';c=getchar());for(;c>='0'&&c<='9';r=(r<<1)+(r<<3)+(c^48),c=getchar());}
    const int maxn = 1e5 + 10;
    const int maxm = 21;
    int n,m;
    int a[maxn];
    int num[maxm];
    int s[maxn][maxm];
    int len[1<<maxm];
    int dp[1<<maxm];
    int dfs(int status)
    {
        if(dp[status] != 1<<30)
            return dp[status];
        for (int i = 1; i <= m; ++i)
            if(status & (1<<(i-1)))
            {
                int l = len[status] - num[i] + 1,r = len[status];
                dp[status] = min(dp[status],dfs(status ^ (1<<(i-1))) + num[i] - (s[r][i] - s[l-1][i]));
            }
        return dp[status];
    }
    int main()
    {
        read(n);
        read(m);
        for(int i = 1;i<=n;++i)
            read(a[i]),num[a[i]]++,s[i][a[i]]++;
        for(int i = 1;i<=n;++i)
            for(int j = 1;j<=m;++j)
                s[i][j] += s[i-1][j];
        int maxs = (1<<m) - 1;
        for(int i = 1;i<=maxs;++i)
            dp[i] = 1<<30;
        for(int i = 1;i<=maxs;++i)
            for(int j = 1;j<=m;++j)
                if(i & (1<<(j-1)))
                    len[i] += num[j];
        printf("%d",dfs((1<<m)-1));
        return 0;
    }
    
  • 相关阅读:
    Composite in Javascript
    Model Validation in Asp.net MVC
    HttpRuntime.Cache vs. HttpContext.Current.Cache
    Controller Extensibility in ASP.NET MVC
    The Decorator Pattern in Javascript
    The Flyweight Pattern in Javascript
    Model Binding in ASP.NET MVC
    Asp.net MVC
    jQuery Ajax 实例 全解析
    ASP.NET AJAX入门系列
  • 原文地址:https://www.cnblogs.com/Clouder-Blog/p/lp3694.html
Copyright © 2011-2022 走看看