DP大乱炖

思维型:

ABC263-D - Left Right Operation

题意:

给你n个数, L, R. 每次操作你可以选择从左到右任意选择连续的一段 (1~n) 让对应的数变成L. 也可以从右向左任意选择连续一段变成R.  但这两段不能重合 可以进行任意次操作. 问你最后可以得到的这些数的最小总和是多少?

思路:

赛时想偏了钻了牛角尖. 其实对于这个题目显然不可能出现交叉覆盖(重合)的情况 因此我们从一侧开始枚举搞个线性dp就可以过了. 看代码就理解了应该.

code:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
ll a[N], dp[N];
ll n, L, R;
ll ans;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> L >> R;

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        ans += a[i];
    }

    // dp 表示到第i位, 能得到的最小和.
    // 显然这个题目不能出现左右交叉的情况, 因此我们采用的方法就是只枚举一侧l进而更新另一侧r

    for (int i = 1; i <= n; i++) {
        //枚举左面来更新. 不用担心有的情况覆盖不到, 在下面有着dp[i - 1] + a[i]起到了这个作用
        ans = min(ans, dp[i - 1] + (n - i + 1) * R);
        //比较选择最优情况
        dp[i] = min(dp[i - 1] + a[i], i * L);
    }

    //上述操作没有更新i == n 的情况其实也可以直接把循环改成 from 1 to n + 1
    ans = min(ans, dp[n]); 
    cout << ans << "\n";
    
    return 0;
}

牛客小白月赛54E.Slash

题意:

问你从原点出发每次只能向下/右走, 求路线形成的字符串包含给定字符串s的最大数量是多少.

思路:

这个题主要考虑每个i,j点可能对应s串中的多个位置, 因此我们需要开三维dp来处理. dp[i][j][k]用来表示, 在i点i, j, 且与字符串s已经匹配了k个, 所能得到的包含给定字符串s的最大数量.

1.初始化dp数组为负数. 注意开始时候设置dp[1][0][0] = dp[0][1][0] = 0, 这两个点一定会被用到!

2.考虑转移方程. 想到只能由左, 和上方转移过来得到dp[i][j][k] = max( dp[i][j][k], dp[i-1][j][k-1], dp[i][j-1][k-1] )

3.考虑转移方程中k=1时, 是特殊情况, 需要由之前状态的最大值赋值给dp[i][j][1]. 进而考虑用什么表示之前状态下的最大值, 想到了用dp[i][j][0] 表示每个状态下的最大值.

dp[i][j][0] = max( dp[i][j][len], dp[i-1][j][0], dp[i][j-1][0] )

进而dp[i][j][1] = max( dp[i][j][1], dp[i-1][j][0], dp[i][j-1][0])

4.整体框架已经搭建完成, 但由以下两点注意

1)注意特判s的长度为1的情况

2)如果s[k] != a[i][j] 可以直接跳过的原因是: 不形成匹配, 这个点的最大值是max(dp[i-1][j][0], dp[i][j-1][0]) 不会影响其他的匹配! 也不会影响答案, 因此能跳过.

但有一个问题:

我看出题人都在最后遍历了下for (int i = 0; i <= len; i++) ans = max(ans, dp[n][m][i])

感觉没必要啊,  毕竟已经用了dp[i][j][0]表示了...

可能是我错了?有什么地方没想到?

code:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int dp[105][105][105];
char a[105][105];
char s[105];

signed main() {
    int n, m;
    scanf("%d %d", &n, &m);
    scanf("%s", s + 1);
    for (int i = 1; i <= n; i++)
        scanf("%s", a[i] + 1);
    int len = strlen(s + 1);

    memset(dp, -0x3f, sizeof dp);
    dp[0][1][0] = dp[1][0][0] = 0;

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            for (int k = 1; k <= len; k++) {
                if (a[i][j] != s[k])
                    continue;
                if (k == 1 && len != 1)
                    dp[i][j][1] = max(dp[i][j][1], max(dp[i-1][j][0], dp[i][j-1][0]) );
                else
                    dp[i][j][k] = max(dp[i][j][k], max(dp[i-1][j][k-1], dp[i][j-1][k-1]) + 1 );
            }
            dp[i][j][0] = max(dp[i][j][len], max(dp[i-1][j][0], dp[i][j-1][0]) );
        }
    printf("%d\n", dp[n][m][0]);

    return 0;
}

状压:

Hie with the Pie TSP问题 floyd + 状压dp

题意:

让你从1出发遍历所有点最后回到1, 求此过程最小花费.

思路:

因为两点之间不一定只能通过他所给的得到最小值.

比如 1-> 2 cost = 3, 1-> 3 cost = 1. 3 ->2 cost = 1. 那么其实从1->3的最小花费是2. 因此我们想到用floyd解决最短路.

然后我们思考dp数组应该表示什么? 图问题一般都会有一个维度表示状态因此我们第一个维度就设置成状态表示每个点选或者不选, 第二个维度应该表示什么呢? 结合题目中的要求想到可以表示当前所在的点. 因此我们dp[s][i]表示的就是在s状态下, 当前的点是i时的最小花费!

最后我们构建转移方程, 我们枚举起点和终点即可, 方程如下非常明朗.

即对于每个状态s, 枚举起点i, 终点j, 异或相当于移除j点. 即对每个状态下的终点j, 我们检验移除这个点后, 用其他的点向着个点移动的最小距离.

f[s][j] = min(f[s][j], f[s \bigoplus (1 << j)][i] + w[i][j]);

tips:

我写的时候nt了, 有两个点需要注意orz

1.注意memset f 为无穷大后, 需要初始化那些开始只有一个点的位置, 那些点只能由原点而来.

这里有两种写法, 见代码里的注释!

2.TSP问题有个关键的点就是, 你的状态方程最后表示的只是从起点走出去, 而不包括走回来, 因此还要枚举加上走回来的花费! 我nt的地方还在于w[i][0] 写成了w[0][i], 还眼瞎了debug de到崩溃...

code:

#include <iostream>
#include <cstring>

using namespace std;
const int INF = 0x3f3f3f3f;

int w[11][11];
int f[1<<11][11]; //f[s][i]->表示当前s状态且在i点时最小cost

int n;

void solve() {
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cin >> w[i][j];

    //floyd
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                w[i][j] = min(w[i][j], w[i][k] + w[k][j]);
    //INF
    memset(f, 0x3f, sizeof f);
    
    /*
    写法1
    如果状态只有这一个点 那么一定是从原点到这个点
    */
    for (int i = 0; i < n; i++)
        f[1 << i][i] = w[0][i];
    /*
    写法2
    直接初始化最开始的点, 其他的交给程序递推出来
    f[1][0] = 0;
    */
    
    for (int s = 0; s < (1 << n); s++)
        for (int i = 0; i < n; i++) if (s & (1 << i)) // from
            for (int j = 0; j < n; j++) if (s & (1 << j))  //to
                if (i != j)
                    f[s][j] = min(f[s][j], f[s ^ (1 << j)][i] + w[i][j]);
            
    int ans = INF;
    for (int i = 1; i < n; i++) 
        ans = min(ans, f[(1 << n) - 1][i] + w[i][0]); //注意w[i][0] 而不是w[0][i] !!!!!

    cout << ans << "\n";
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    while (cin >> n && n != 0) {
        n ++;
        solve();
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值