#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;
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/Cao-Yucong/p/12586597.html
今日推荐