• ACM学习历程—HDU1584 蜘蛛牌(动态规划 && 状态压缩 || 区间DP)


    Description

    蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么这些牌也跟着一起移动,游戏的目的是将所有的牌按同一花色从小到大排好,为了简单起见,我们的游戏只有同一花色的10张牌,从A到10,且随机的在一行上展开,编号从1到10,把第i号上的牌移到第j号牌上,移动距离为abs(i-j),现在你要做的是求出完成游戏的最小移动距离。
     

    Input

    第一个输入数据是T,表示数据的组数。
    每组数据有一行,10个输入数据,数据的范围是[1,10],分别表示A到10,我们保证每组数据都是合法的。
     

    Output

    对应每组数据输出最小移动距离。
     

    Sample Input

    1
    1 2 3 4 5 6 7 8 9 10
     

    Sample Output

    9

    题目要求是讲10张牌合并成一堆,然后小牌放在大小+1的大牌上。

    这题关键是只有10张牌,那么每张牌在自己位置上,或者在其他位置上,那么设某一位为1表示牌在自己位置上,否则为0,这样就能进行状态压缩了。

    p[state],state的二进制位就是每张牌的状态。p表示最优解

    其次就是考虑到如果2不在自己位置上,3不在自己位置上,而4在自己位置上,那么234肯定在4的位置上,因为最多就是先3到4,然后2到3,或者先2到3,然后2、3一起到4(1不考虑的情况)

    而且从中还能得到,某一位值为k的牌,可以放到k+1,k+2,k+3中第一个非0位置上。

    这样状态更新就是

    p[state^(1<<i)] = myMin(p[state^(1<<i)], p[state]+abs(i-j));(其中i是state的非0位,j是i以上第一个非0位)

    myMin是自定义最小值,对第一个参数为-1的情况进行了特判,这里处理方式很多。

    然后这个方程我不太会用for完成。用了spfa的思想前一个状态更新了会导致后面的状态更新。

    代码:

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <algorithm>
    #define LL long long
    
    using namespace std;
    
    int a[15], p[4100], from;
    int Hash[15];
    bool vis[4100];
    
    int myMin(int x, int y)
    {
        if (x == -1)
            return y;
        return min(x, y);
    }
    
    void input()
    {
        memset(p, -1, sizeof(p));
        memset(vis, false, sizeof(vis));
        from = 0;
        for (int i = 1; i <= 10; ++i)
        {
            scanf("%d", &a[i]);
            Hash[a[i]] = i;
            from |= (1<<i);
        }
        p[from] = 0;
    }
    
    void spfa()
    {
        int k, v, pre;
        queue<int> q;
        q.push(from);
        vis[from] = true;
        while (!q.empty())
        {
            k = q.front();
            q.pop();
            vis[k] = false;
            for (int i = 1; i <= 10; ++i)
            {
                if ((k & (1<<i)) && a[i] != 10)
                {
                    v = a[i]+1;
                    while (!(k&(1<<Hash[v])))
                        v++;
                    pre = p[k^(1<<i)];
                    p[k^(1<<i)] = myMin(p[k^(1<<i)], p[k]+abs(i-Hash[v]));
                    if (p[k^(1<<i)] != pre && !vis[k^(1<<i)])
                    {
                        q.push(k^(1<<i));
                        vis[k^(1<<i)] = true;
                    }
                }
            }
        }
    }
    
    void work()
    {
        spfa();
        printf("%d
    ", p[1<<Hash[10]]);
    }
    
    int main()
    {
        //freopen("test.in", "r", stdin);
        int T;
        scanf("%d", &T);
        for (int times = 0; times < T; ++times)
        {
            input();
            work();
        }
        return 0;
    }
    View Code

    听了小烈的做法后@hqwhqwhq,发现可以用区间DP,这样n很大也可以解决了。

    因为对于1到n这段连续的数,必然最后一次合并肯定是1~k移动到k+1~n上去。

    那么自然可以枚举k来进行区间DP,需要注意的是DP若使用for循环,需要写成从小到大枚举区间长度来进行DP,否则需要记忆化搜索。

    代码:

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cmath>
    #include <cstring>
    #include <algorithm>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #define LL long long
    
    using namespace std;
    
    const int maxN = 105;
    int n, a[maxN];
    int p[maxN][maxN];
    
    void input()
    {
        memset(p, -1, sizeof(p));
        scanf("%d", &n);
        int k;
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &k);
            a[k] = i;
            p[i][i] = 0;
        }
    }
    
    inline int myMin(int x, int y)
    {
        if (x == -1) return y;
        else return min(x, y);
    }
    
    void work()
    {
        for (int j = 1; j < n; ++j)
            for (int i = 1; i+j <= n; ++i)
                for (int k = i; k < i+j; ++k)
                    p[i][i+j] = myMin(p[i][i+j], p[i][k]+p[k+1][i+j]+abs(a[k]-a[i+j]));
        printf("%d
    ", p[1][n]);
    }
    
    int main()
    {
        //freopen("test.in", "r", stdin);
        int T;
        scanf("%d", &T);
        for (int times = 1; times <= T; ++times)
        {
            input();
            work();
        }
        return 0;
    }
    View Code
     
  • 相关阅读:
    oracle基于事件的调度简单学习
    ALTER SEQUENCE
    初识oracle嵌套表
    关于索引失效的一点学习
    Oracle的job用法简单小结
    oracle中的FLASHBACK TABLE 和查询闪回的一点认识
    数据库事务的学习(二)
    电动车驱动力与行驶阻力平衡图.png
    matlab 等高线contour
    IPMSM弱磁控制策略
  • 原文地址:https://www.cnblogs.com/andyqsmart/p/4756631.html
Copyright © 2020-2023  润新知