• DP重新学


    白书上的DP讲义: 

    DAG上的dp 不要好高骛远去学这种高端东西,学了也写不对,剩下的几天把基本的dp和搜索搞下,就圆满了。不要再学新算法了,去九度把现有的算法写个痛。


    学了数位DP和记忆搜索,今天遇到一道标准的水题,搞了三小时还没搞出来真是讽刺啊:

    座位问题

    分析:

    1.在任何时候,一个女生的左边或者右边至少有一个女生,这就是说若只有1个人,则这个人必须为男生。(竟然一直没意识到这点,我能说我要是看明白了这一行,就能ac吗?)

    2.有n个人的排列,则应该从第n个人开始往第一个人推。这样思路就比较明确:不用去管第一个人究竟是男生还是女生,只用根据第n个人的性别,往前推。

    第n个人可以是男生也可以是女生:总的排列数 = 第n个人是男生的情况 + 第n个人是女生的情况。

    用dp[n][0]表示,有n个人的排列,排在第n个的是女生。用dp[n][1]表示,排在第n个的是女生

    则: dp[n][0]应该加上dp[n-1][0],因为若n-1个人的排列中第n-1个为女生,则外面还可以再坐一个女生。还应加上dp[n-2][1],因为若第n-2个人为男生,则可以再坐2个女生。

    #include <cstdio>
    
    const int MOD = 1000000007;
    const int MAXN = 1001;
    int dp[MAXN][2];
    //dp[i][0] i个人,第一个为女生
    //dp[i][1] i个人,第一个为男生  
    
    void init() {
        //只有1个人时,必须为男生 
        dp[1][0] = 0; 
        dp[1][1] = 1;
        dp[2][0] = 1;
        dp[2][1] = 1;
        for (int i = 3; i < MAXN; ++i) {
            //若最前面为女生,则前面还可以坐一个女生,
            //最前面为男生,则可以做2个女生 
            dp[i][0] = (dp[i - 1][0] + dp[i - 2][1]) % MOD;
            dp[i][1] = (dp[i - 1][0] + dp[i - 1][1]) % MOD;
        }
    }
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n;
        init();
        while (scanf("%d", &n) != EOF) {
            printf("%d
    ", (dp[n][0] + dp[n][1]) % MOD);
        }
        return 0;
    }
    View Code

    自己用记忆搜索搞的:

    #include <cstdio>
    #include <memory.h>
    
    const int MOD = 1000000007;
    int dp[1001][2];
    
    int dfs(int x, int t) { //第一个是男 || 女
        if (x == 1 && t == 0) return 0; // <--------------1个人只可能是男 
        if (x == 1 && t == 1) return 1;
        if (x == 2) return 1;
        if (dp[x][t] != 0) return dp[x][t];
        long long ret = 0;
        if (t == 0) {  //前一个是女
            ret += dfs(x - 1, 0);
            ret += dfs(x - 2, 1); // 
        } else {
            ret += dfs(x - 1, 1);
            ret += dfs(x - 1, 0);
        }
        dp[x][t] = ret % MOD; 
        return dp[x][t];
    }
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif    
        int n;
        while (scanf("%d", &n) != EOF) {
            if (n == 1) {
                puts("1");
                continue;
            }
            memset(dp, 0, sizeof dp);
            printf("%d
    ", (dfs(n, 0) + dfs(n, 1)) % MOD);
        }
        return 0;
    }
    View Code

    实际上ret不必要用long long,2 * MOD < 2 ^ 31 - 1。

    最长递增子序列:今天遇到2道这种题,都写错了。突然想起来这不是王道八套模拟上面的原题?当时是逐项相减变成最长连续子序列和的问题。现在竟然不会了。

    传统的dp算法O(n^2)数据大于10000时会超时,故需要改进:不用动态规划的方法。

    维护一个数组dp[n],令dp[len]表示长度为len的子序列的最末尾的数字的最小值:(勉强想明白过程,还没想好该怎么表达)

    1.由更新规则知:dp[i]严格递增,dp[2] > dp[1]必然成立。

    2.对于每个新的数字k,必能找到一个长度为i的序列,使得其最后面的数字为k,这时就要更新dp[1...n]数组,使得dp[i] = k,那么

    3.

    #include <cstdio>
    
    const int MAXN = 100001;
    
    inline int Max(int a, int b) {
        return a > b ? a : b;
    }
    
    int len;
    int dp[MAXN];
    //查找下界 
    int find(int x) {
        int left = 1;
        int right = len;
        while (left < right) {
            int mid = left + (right - left) / 2;  
            if(dp[mid] >= x) right = mid;  
            else left = mid+1;  
        }
        return left;
    } 
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n;
        while (scanf("%d", &n) != EOF) {
            int num[MAXN];
            for (int i = 1; i <= n; ++i) scanf("%d", &num[i]);
            len = 1;
            dp[len] = num[1];
            for (int i = 2; i <= n; ++i) {
                if (num[i] > dp[len]) {
                    dp[++len] = num[i];
                } else {//更新 
                    int pos = find(num[i]);
                    dp[pos] = num[i];
                }
            }
            printf("%d
    ", len);
        }
        return 0;
    }
    View Code

     首尾相连数组的最大子数组和

    一看到这题我就瞬间定式思维去循环扫描的求最大子数组和,果断WA。

    对于这样的一组数据:1 10 -4 -5 5 5  若循环扫描求最大子数组和,就会计算为: 1+....+5 = 12。实际结果是21,除去中间的-4,-5。

    因为lcs的dp状态转移方程为: dp[i] = max(dp[i - 1] + a[i], a[i]),最后再比较dp[i]找出最大值...对于循环数组,中间的-4,-5可以跳过去,从5开始计算到1,10。

    所以应先计算:max1 = a[0] ...a[n]的最大子列和,然后计算max2 = a[i],…a[n-1],a[0],…,a[j]这种情况下的最大子列和,最终结果为max(max1, max2)。

    max2的计算可以为:总的数组和 - 最小负数子列和(将整个数组取反后为最大正数子列和)。

    #include <cstdio>
    
    const int MAXN = 100001;
    
    int lcs(int a[], int n) {
        int sum = 0;
        int ret = 0;
        for (int i = 0; i < n; ++i) {
            if (sum > 0) sum += a[i]; 
            else sum = a[i];
            if (sum > ret) ret = sum;
        }
        return ret;
    } 
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n;
        while (scanf("%d", &n) != EOF) {
            int a[MAXN];
            for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
            //先求出连续数组的最大子序列和
            int max1 = lcs(a, n);
            int sum = 0;
            for (int i = 0; i < n; ++i) { //将数组取反 
                sum += a[i];
                a[i] = -a[i];
            }
            //再找出最小子序列和
            int max2 = lcs(a, n);
            max2 = sum + max2;
            printf("%d
    ", max1 > max2 ? max1 : max2); 
        } 
    
        return 0;
    }
    View Code

     股票交易

    这题贪心应该能ac,昨天贪心过了4个测试点。dp的方法需要优化一下,不然一个测试点就直接10 ^ 9超时。

    状态转移方程:dp[i][j](表示前j天进行了i次交易),则

    首先找出 1.2...k,k <= j - 1中哪一天买进然后第j天卖出时收益最大,第j天可以选择不卖或者卖出(第k天买进)

    dp[i][j] = max(dp[i][j-1], dp[i-1][k] + a[j] - a[k]);令tmp = MAX(dp[i-1][k] + a[j] - a[k]),考虑到tmp 中dp[i-1][j]-a[k]只要保持1...j中的最大值就行,故不需要每次都单独循环1...j计算一下tmp。就能将三层循环优化到2层。

    #include <cstdio>
    #include <memory.h> 
    
    const int MAXN = 1001;
    
    inline int Max(int a, int b) {
        return a > b ? a : b;
    }
    int dp[MAXN][MAXN]; //前j天进行i次交易,的最大收益
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n, k; 
        while (scanf("%d%d", &n, &k) != EOF) {
            int a[MAXN];
            for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
            memset(dp, 0, sizeof dp);
            for (int i = 1; i <= k; ++i) { //将三维dp优化到二维 
                int tmp = dp[i][1] - a[1]; // tmp = max(dp[i-1][k] - a[k]) K = 1...j-1 
                for (int j = 2; j <= n; ++j) {
                    dp[i][j] = Max(tmp + a[j], dp[i][j]);
                    dp[i][j] = Max(dp[i][j - 1], dp[i][j]);
                    if (dp[i - 1][j] - a[j] > tmp) tmp = dp[i - 1][j] - a[j];                
                }
            }
            printf("%d
    ", dp[k][n]);
        }
        return 0;
    }
    View Code

     Jobdu MM分水果

    dp解法:

    #include <cstdio>
    #include <memory.h>
    
    bool dp[10000000]; 
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n;
        int wi[101];
        while (scanf("%d", &n) != EOF) {
            int total = 0; 
            for (int i = 1; i <= n; ++i) {
                scanf("%d", &wi[i]);
                total += wi[i];
            } 
            memset(dp, false, sizeof dp);
            int half = total / 2;
            dp[0] = true;
            for (int i = 1; i <= n; ++i)
                for (int j = half; j >= wi[i]; --j)
                    dp[j] = dp[j] || dp[j - wi[i]];
            for (int i = half; i >= 0; --i) {
                if (dp[i]) {
                    printf("%d
    ", total - 2 * i);
                    break; 
                } 
            }
        }
        return 0;
    }
    View Code

    dfs剪枝:

    #include <cstdio>
    #include <algorithm> 
    
    int half;
    int ans;
    int w[101];
    int sum[101];
    
    //找出<=half可以取到的最小值 
    void dfs(int i, int pack) {
        if (pack > half || pack + sum[i] < ans) return; //把比当前值小的剪掉 
        if (pack > ans) ans = pack;
        if (i == -1) return;
        if (ans == half) return;
        dfs(i - 1, pack + w[i]);
        dfs(i - 1, pack);
    } 
    
    int main() {
    #ifndef ONLINE_JUDGE
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
    #endif
        int n;
        while (scanf("%d", &n) != EOF) {
            int total = 0;
            for (int i = 0; i < n; ++i) {
                scanf("%d", &w[i]);
                total += w[i];
            }
            std::sort(w, w + n);
            sum[0] = w[0];
            for (int i = 1; i < n; ++i) {
                sum[i] = sum[i - 1] + w[i];
            }
            half = total / 2;
            ans = 0; 
            dfs(n - 1, 0);
            printf("%d
    ", total - 2 * ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    隐藏TabControl的标签 上海
    最近写的一个存储过程 上海
    DBUS 介绍 上海
    存储过程 几个小例子 上海
    C# BHO 上海
    EXEC和sp_executesql的区别 上海
    office文档转换成mht文档(准备、原理篇) 上海
    Dictionary 排序 上海
    .Net 开源资源 上海
    Geant4.9.5.p01 in ubuntu12.04 OpenGL driver.
  • 原文地址:https://www.cnblogs.com/fripside/p/3589489.html
Copyright © 2020-2023  润新知