• #QBXT2020 3月DP营 Day1上午


    QBXT DP精讲

    DP的实质是图论。

    状态对对应节点,转移对应边

    DP的三要素是 状态、转移、初始化。

    斐波那契数列:

    用其他状态计算这个状态。

    f[0] = 0,f[1] = 1;
    for(int i = 2;i <= n; i++) 
        f[i] = f[i-1] + f[i-2];
    

    用这个状态计算其他状态。

    f[0] = 0,f[1] = 1;
    for(int i = 0;i <= n; i++){
        f[i+1] += f[i];
        f[i+2] += f[i];
    }
    

    有的题只能用以上两种方法当中的某一种,所以全部都要掌握。

    甚至还有反人类的dfs用法。

    int dfs(iny n){
        if(n == 0) return 0;
        if(n == 1) return 1;
        return dfs(n-1) + dfs(n-2);
    }
    

    因为代码里面的数字只有0、1,所以f(x)就一定是f(x)个1加起来,复杂度就是O(f(x)),大概等于(2^n)

    记忆化搜索

    int dfs(int n){
        if(n == 0) return 0;
        if(n == 1) return 1;
        if(g[n]) return f[n];
        f[n] = dfs(n-1) + dfs(n-2);
        g[n] = 1;
        return f[n];
    }
    
    • 特征方程法

    假设数组(f_n = f_{n-1} + f_{n-2}),求通项公式。

    我们可以对应假设(x^2 = x + 1),解得x的值,x就是系数,得到方程(a_n = y x_1^n + z x_2^n),把a[0],a[1]代入就好。

    组合数

    杨辉三角

    for(int i = 0;i <= n; i++){
        c[i][0] = 1;
        for(int j = 1;j <= i; j++)
            c[i][j] = c[i-1][j-1] + c[i-1][j];
    }
    
    for(int i = 0;i <= n; i++){
        c[i][0] = 1;
        for(int j = 1;j <= i; j++){
            c[i+1][j] += c[i][j];
            c[i+1][j+1] += c[i][j];
        }
    }
    

    路径方案数

    N*M的方格图,只能向右或者向下,走到右下的方案数?走到右下的最小代价?

    [f[i][j] = f[i][j-1] + f[i-1][j] ]

    也就是上面的杨辉三角(组合数)

    [C_{n+m}^n ]

    数字三角形

    [f[i][j] = max(f[i-1][j-1],f[i-1][j]) + a[i][j]; ]

    数字三角形2

    要求路径上的数模100最大。

    用之前的方法会破坏最优子结构。

    重要技巧:题目没多一个条件,状态就多加一个维度。

    由之前的f[i][j]变成f[i][j][k],表示走到(i,j)的和模100是否等于k。

    转移:

    for(int i = 1;i <= n; i++){
        for(int j = 1;j <= n; j++){
            for(int k = 0;k < 100; k++){
                if(f[i][j][k]){
                    f[i+1][j][(k+a[i+1][j])%100] = 1;
                    f[i+1][j+1][(k+a[i+1][j+1])%100] = 1;
                }
            }
        }
    }
    

    (方法是用自己转移别人)

    最长上升子序列 (LIS longest increasing subsequence)

    这个名字好牛逼

    第一种,时间复杂度为(O(n^2))

    for(int i = 1;i <= n; i++){
        f[i] = 1;
        for(int j = 1;j <= j; j++){
            if(a[j] < a[i]){
                f[i] = max(f[i],f[j]+1);
            }
        }
    }
    
    memset(f,1,sizeof f);
    for(int i = 1;i <= n; i++){
        for(int j = i+1;j <= n; j++){
            if(a[j] > a[i]){
                f[j] = max(f[j],f[i]+1);
            }
        }
    }
    

    第二问:求方案数,输出方案

    再开一个数组,g[i]表示方案数,pre类似链表结构表示i的上一个数

    for(int i = 1;i <= n; i++){
        f[i] = 1;
        g[i] = 1;
        pre[i] = 0;
        for(int j = 1;j <= j; j++){
            if(a[j] < a[i]){
                // f[i] = max(f[i],f[j]+1);
                int l = f[j] + 1;
                if(l > f[i]) f[i] = l,g[i] = 0,pre[i] = j;
                if(l == f[i]) g[i] += g[j];
            }
        }
    }
    
    memset(f,1,sizeof f);
    memset(g,1,sizeof g);
    for(int i = 1;i <= n; i++){
        for(int j = i+1;j <= n; j++){
            if(a[j] > a[i]){
                // f[j] = max(f[j],f[i]+1);
                int l = f[i] + 1;
                if(l > f[j]) f[j] = l,g[j] = 0,pre[j] = i;
                if(l == f[j]) g[j] += g[i];
            }
        }
    }
    

    输出

    do{
        z[cnt++] = p;
        p = pre[p];
    }while(p);
    reverse(z + 1,c + cnt + 1);// 翻转
    print;
    

    第二种:线段树

    定义一个线段树 1 - m,m = max(a1,12,a3,……an)。把a[j]的位置存储f[a[j]],每次寻找的时候查询max(1,a[i]-1)就可以。以为是从前往后的运算,所以不会产生后面小的数算到前面的问题。

    第三种:二分

    若存在(p_1 < p_2),且(a_{p_1} > a_{p_2},f_{p_1} < f_{p_2}),则(a_{p_1})就失去了意义,就可以删掉。

    怎么找到这样的数呢?

    对于数组a,如果只要存在上述的数,就把(p_1)去掉,用(p_2)替换。那么最终的数组一定满足:z[x]表示f[a[i]] = x的最大的a[i]。、

    代码:z数组表示上面那一行的a[i]的位置。

    cnt = 0;
    for(int i = 1;i <= n; i++){
        f[i] = 1;
        for(int j = 1;j <= cnt; j++){
            if(a[z[j]] < a[i]) f[i] = max(f[i],j+1);
    
            if(f[i] > cnt) cnt++,z[cnt] = i;
            else{
                if(a[i] < a[z[f[i]]]) z[f[i]] = i;
            }
        }
    }
    

    滑雪

    N行M列的图,每个格子有高度,可以滑向周围四个比自己矮的格子,最远能滑多远。

    记忆化搜索

    f[i][j] = max(f[x][y]) + 1,(x,y)与(1,j)的曼哈顿距离为1。

    DP(降维到LIS)

    ①把所有点的高度从小到大排序,根据规定,滑雪方向在数组里一定是从左向右滑,考虑做从左向右DP。

    到f[i],查找左边所有临近的最大值,进行DP。

    关键:确定DP顺序。先把无需数列变成有序数列。然后再做。

    出现拓扑排序的题很可能用DP

    憨八龟

    每张牌的数量是有限制的。变化的量作为状态的维度。

    用一个i表示我们调到哪里了,以及用了几张各种样式的牌(a1,a2,a3,a4)。暴力定出来状态是(f[i][a1][a2][a3][a4][a5])

    我们可以知道五维的四个数有的关系a1 + 2a2 + 3a3 + 4a4 = i,这样就降成了四维的DP。

    重点:冗余变量的去除。

    优化

  • 相关阅读:
    判断操作系统多久没有任何操作
    初识类的方法
    类引用
    将窗体显示在 PageControl 上。
    用批处理命令安装打印机
    减小Delphi2010程序的尺寸(关闭RTTI反射机制)
    Delphi 的编码与解码(或叫加密与解密)函数
    c# 让repeater多列显示
    合并动态数组
    [最新]Visual Assist X 破解版下载(10.6.1827)
  • 原文地址:https://www.cnblogs.com/Cao-Yucong/p/12586597.html
Copyright © 2020-2023  润新知