DP经典例题2


1.CF24D Broken robot(期望DP)

   (1)题目描述:

         (2)解题思路:

                  首先分析题目,题目说我们从点(x,y)最后到(n,m)的期望数学步数,很明显这是一个期   望Dp。我们分析操作选择,可以走左,走右,或者向下,或者不动。意味着我们在某些边界,只能有三种选择,不是边界又可以有四种操作。

                  我们从n - 1行开始向x枚举,对于第j列有三种走的情况。

                        1.当j=1,我们只能向右走或向下走或不动

                        2.当1<j<m时,我们有四种选择。

                        3.当j=m时,我们只能向左走或者向下走或不动

                  我们用f[i][j]表示从i行j列走到n行这个点的期望步数是多少。

                       对于第一种情况的状态表示是f[i][j] = 1 / 3 * (f[i + 1][j] + f[i][j] + f[i][j + 1]) + 1

                       对于第二种情况的状态表示是f[i][j] = 1 / 4 * (f[i +1][j] + f[i][j] + f[i][j + 1] + f[i][j - 1]) + 1

                       对于第三种情况的状态表示是f[i][j] = 1 / 3 * (f[i + 1][j] + f[i][j - 1] + f[i][j]) + 1

               

                 对式子做等价变形可以得到如下系数矩阵:

            因此我们可以使用高斯消元法对每一行进行消元,解出每一行的第j个元素。因为我们这里矩阵是有规律的,并且我们消元其实最终是2m项,因此可以使用简便版的高斯消元来解这个系数矩阵。因此该解法的时间复杂度为O(nm)

        (3)代码实现

#include "bits/stdc++.h"
using namespace std;
const int N = 1010;
double f[N][N],a[N][N];
int n,m;
void gauss()
{
    //把每一行首个非零元素变为0
    for(int i = 1;i <= m;i++) {
        double r = a[i + 1][i] / a[i][i];
        a[i + 1][i] = 0;
        a[i + 1][i + 1] -= r * a[i][i + 1];
        a[i + 1][m + 1] -= r * a[i][m + 1];
    }
    //从后往前化成行标准型矩阵
    for(int i = m;i >= 1;i--) {
        double r = a[i - 1][i] / a[i][i];
        a[i - 1][i] = 0;
        a[i - 1][m + 1] -= r * a[i][m + 1];
    }
}
int main()
{
    int x,y;
    cin >> n >> m;
    cin >> x >> y;
    //只有1列的时候,只能向下或者原地不动
    if(m == 1) {
        cout << fixed << setprecision(4) << 2.0 * (n - x) << endl;
        return 0;
    }
    else {
        for(int i = n - 1;i >= x;i--) {
            a[1][1] = a[m][m] = 2.0 / 3;
            a[1][2] = a[m][m - 1] = -1.0 / 3;

            a[1][m + 1] = 1 + f[i + 1][1] / 3;
            a[m][m + 1] = 1 + f[i + 1][m] / 3;  
            //对每一行的值进行高斯消元求解
            for(int j = 2;j < m;j++) {
                a[j][j - 1] = a[j][j + 1] = -1.0 / 4;
                a[j][j] = 3.0 / 4;
                a[j][m + 1] = f[i + 1][j] / 4 + 1;
            }
            gauss();
            for(int j = m;j >= 1;j--) f[i][j] = a[j][m + 1] / a[j][j];
        }
    }
    cout << fixed << setprecision(4) << f[x][y] << endl;
    return 0;
}

2.CF19B Checkout Assistant(01背包变种)        

      (1)题目描述

         (2)解题思路

            这道题和01背包类似,不过这题是让我们求最小值。因此我们初始化所有值为最大,表示我们i秒最少需要支付的最小金额是多少。这题特殊的是我们的时间最少有n秒,做多为n + mx(物品扫描的最大时间)。(其实就是换了个角度,想商家最少能扫描多少件物品的价值是多少等价于小偷最少要给的钱)

             状态转移方程为:dp[j] = min(dp[j],dp[j - v[i]] + w[i])

        (3)代码实现

#include "bits/stdc++.h"
using namespace std;
long long f[4050];
int v[4050],w[4050];
int main()
{
    int n,mx = -1;
    cin >> n;
    memset(f,0x3f,sizeof(f));
    f[0] = 0;
    for(int i = 1;i <= n;i++) {
        cin >> v[i] >> w[i];
        v[i]++;
        mx = max(mx,v[i]);
    }
    mx += n;
    for(int i = 1;i <= n;i++) {
        for(int j = mx;j >= v[i];j--) {
            f[j] = min(f[j],f[j - v[i]] + w[i]);   
        }
    }
    long long res = 1e18;
    for(int i = n;i <= mx;i++) res = min(res,f[i]);
    cout << res << endl;
    return 0;
}

3.CF11D A Simple Task(状压DP)

        (1)题目描述

        (2)解题思路

                 这道题很容易看出来是一道NP问题,很难有其他好的解法,因此我们只能枚举,首先枚举一个点状态集,规定最小的点为出度点(防止重复),通过枚举j点和k点,来判断我们是否存在环。

         (3)代码实现

#include "bits/stdc++.h"
using namespace std;
int g[25][25];
long long f[1 << 20][25];
int lowbit(int x)
{
    return x & -x;
}
int main()
{
    int n,m,T;
    cin >> n >> m;
    T = m;
    while(T--) {
        int u,v;
        cin >> u >> v;
        g[u - 1][v - 1] = 1;
        g[v - 1][u - 1] = 1;
    }
    long long ans = 0;
    //f[i][j]表示i这个状态集中最小点和j的连通数
    for(int i = 0;i < n;i++) f[1 << i][i] = 1;
    for(int i = 1;i < (1 << n);i++) {
        //枚举最小的点
        int mi = log2(lowbit(i));
        for(int j = mi;j < n;j++) {
            //i中最小点到j没有联通
            if(!f[i][j]) continue;
            for(int k = mi;k < n;k++) {
                //j到k不连通
                if(!g[j][k]) continue;
                //如果k点是i状态集中最小点,那么就计入答案
                if((i >> k) & 1) ans += (k == mi) ? f[i][j] : 0;
                else f[i | (1 << k)][k] += f[i][j];
            }
        }
    }
    //减去所有的二元组,再减去相反的
    cout << (ans - m) / 2 << endl;
    return 0;
}

4.CF10D LCIS(最长公共上升子序列+路径记录)

        (1)题目描述

         (2)解题思路

                首先是最长上升序列,再加上最长公共子序列。因此我们钦定方程dp[i][j]表示以b[j]结尾的最长公共上升子序列,我们依次从前往后找到每个以b[j]结尾的并且小于a[i]的最长上升公共子序列,如果后面出现了和a[i]相等的b[j],那么就可以进行状态转移,dp[j] = mx(前j-1个中的最长上升公共子序列) + 1。而路径记录的话,我们只需要记录从哪一个状态转移过来,然后倒退出来就可以了。

        (3)代码实现

#include "bits/stdc++.h"
using namespace std;
int a[510],b[510],p[510],dp[510];
int main()
{
    int n,m;
    cin >> n;
    for(int i = 1;i <= n;i++) cin >> a[i];
    cin >> m;
    for(int j = 1;j <= m;j++) cin >> b[j];
    for(int i = 1;i <= n;i++) {
        int mx = 0,ps = 0;
        for(int j = 1;j <= m;j++) {
            //找到每个以b[j]结尾的前面最大的长度
            if(b[j] < a[i] && mx < dp[j]) {
                mx = dp[j];
                ps = j;
            }
            //如果当前相等,则可以进行一次状态转移:dp[j] = 找到的最大的 + 本身的长度
            if(a[i] == b[j]) {
                if(dp[j] < mx + 1){
                    p[j] = ps;
                    dp[j] = mx + 1;
                }
            }
        }
    }
    int res = 0,now = 0;
    for(int i = 1;i <= m;i++) {
        if(res < dp[i]) {
            res = dp[i];
            now = i;
        }
    }    
    vector<int> vi;
    cout << res << endl;
    while(now) {
        vi.push_back(b[now]);
        now = p[now];
    }
    reverse(vi.begin(),vi.end());
    for(auto x:vi) cout << x << ' ';
    cout << endl;
    return 0;
}

      

5.CF9D How many trees?(树形DP+catalan数变种)

          (1)题目描述

         (2)解题思路

              考虑dp转移方程dp[i][j]表示j个节点凑出不超过i深度的方案数,考虑到这是一棵二叉 树,我们会有左右两颗子树,因此我们还需要一个中间变量k表示左右各有多少个节点的方案数。也就是dp[i][j] += dp[k][i - 1] * dp[j - k - 1][i - 1],最后用前缀和的思想减去高度小于h的那些方案就行了。(dp[n][n] - dp[n][h - 1])

        (3) 代码实现

#include "bits/stdc++.h"
typedef long long ll;
using namespace std;
ll f[40][40];
int n,h;
//catalan数变种
void dp()
{
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= n;j++)
            for(int k = 0;k <= j - 1;k++)
                //k这个子树的节点个数,左边k个,右边就是j - k - 1个,(-1减的是根节点个数)
                f[j][i] += f[k][i - 1] * f[j - k - 1][i - 1];
}
int main()
{
    cin >> n >> h;
    //初始化,空树也算一种方案
    for(int i = 0;i <= n;i++) f[0][i] = 1;
    dp();
    cout << f[n][n] - f[n][h - 1] << endl;
    return 0;
}

6.HDU - 4571 

        (1)题目大意 

         (2)解题思路

                考虑预处理出每个点到点得最短路,然后考虑dp[i][j]表示当前是第i个景点已经用时j得最大满意程度,然后考虑根据每一个地点得最大满意程度从大到小排序,然后dp,dp[i][k] = max(dp[i][k],dp[j][k - g[i][j] - p[i].w] + p[i].v);

         (3)代码实现

#include "iostream"
#include "cstdio"
#include "cstring"
#include "algorithm"
 
using namespace std;
const int N = 310;
int dp[N][N],g[N][N],idd[N];
struct P {
    int id,w,v;
    bool operator < (const P& other) const {
        return v < other.v;
    };
}p[N];
int solve()
{
    int n,m,t,s,e;
    cin >> n >> m >> t >> s >> e;
    for(int i = 0;i < n;i ++) {
        cin >> p[i].w;
        p[i].id = i;
    }
    for(int i = 0;i < n;i ++)  cin >> p[i].v;
    sort(p,p + n);
    for(int i = 0;i < n;i ++) idd[p[i].id] = i;
    for(int i = 0;i <= 100;i ++) {
        for(int j = 0;j <= 100;j ++) 
            g[i][j] = 1e9;
        g[i][i] = 0;
    }
    for(int i = 1;i <= m;i ++) {
        int u,v,w;
        cin >> u >> v >> w;
        u = idd[u],v = idd[v];
        g[u][v] = g[v][u] = min(g[u][v],w);
    }
    for(int k = 0;k < n;k ++) 
        for(int i = 0;i < n;i ++) 
            for(int j = 0;j < n;j ++)
                g[i][j] = min(g[i][j],g[i][k] + g[k][j]);
    memset(dp,-1,sizeof(dp));
    s = idd[s],e = idd[e];
    for(int i = 0;i < n;i ++) {
        for(int j = p[i].w + g[i][s];j <= t;j ++) {
            dp[i][j] = p[i].v;
        }
    }
    for(int i = 0;i < n;i ++) {
        for(int j = 0;j < i;j ++) {
            if(p[i].v == p[j].v) continue;
            for(int k = 0;k <= t;k ++) {
                if(k < g[i][j] + p[i].w) continue;
                if(dp[j][k - p[i].w - g[i][j]] == -1) continue;
                dp[i][k] = max(dp[i][k],dp[j][k - g[i][j] - p[i].w] + p[i].v);
            }
        }
    }
     
    int ans = 0;
    for(int i = 0;i < n;i ++) {
        for(int j = 0;j <= t;j ++) {
            if(j + g[i][e] > t) break;
            ans = max(ans,dp[i][j]);
        }
    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    for(int i = 1;i <= T;i ++) {
        cout << "Case #" << i << ":" << '\n';
        cout << solve() << '\n';
    }
    return 0;
}

题目链接:

CF10D LCIS - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

CF11D A Simple Task - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

CF19B Checkout Assistant - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

CF24D Broken robot - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

CF9D How many trees? - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值