dp刷题板块

本文解析了UVa在线编程平台上的多个经典题目,包括Uva1025的城市列车换乘问题、Uva437的立方体堆叠问题、Uva116的最小路径和问题、Uva12563的最大歌曲播放问题及Uva1625的字符串合并问题。通过详细的代码和思路说明,帮助读者理解并掌握这些算法题目的解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Uva1025
题意:这个城市有n个车站,每隔一段时间会从所有车站的一遍发出一列列车,有一个要从第一个车站出发,目的是在时间T会见车站n 的一个人,需要换乘,问在不同车站换成所需要等待的最短时间是多少

解法:d(i,j)表示在时刻i,你在j车站所需要等待的最长时间,有三种决策,1. 等一分钟、2. 搭乘往右开的车、3. 搭乘往左开的车

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf = 0x3fffffff;
int dp[200 + 5][50 + 5];//在时间t,车站i应该等待的最长时间
int has_train[205][55][2], t[50 + 5];//has_train表示在时间t车站i是否有列车
int d[50 + 5], e[50 + 5];//往右开的第一班车,往左开的第一班车

int main() {
    int n, T, tmp = 1;
    while (scanf("%d%d", &n, &T)&&n) {
        memset(dp, 0, sizeof(dp)); memset(has_train, 0, sizeof(has_train));
        memset(t, 0, sizeof(t)); memset(d, 0, sizeof(d)); memset(e, 0, sizeof(e));

        for (int i = 1; i <= n - 1; i++) scanf("%d", &t[i]);

        int m1; scanf("%d", &m1);
        for (int i = 1; i <= m1; i++) {
            scanf("%d", &d[i]);
            int tmp = d[i];
            for (int j = 1; j <= n - 1; j++) {
                if (tmp <= T) has_train[tmp][j][0] = 1;
                tmp += t[j];
            }
        }
        int m2; scanf("%d", &m2);
        for (int i = 1; i <= m2; i++) {
            scanf("%d", &e[i]);
            int tmp = e[i];
            for (int j = n - 1; j >= 1; j--) {
                if (tmp <= T) has_train[tmp][j + 1][1] = 1;
                tmp += t[j];
            }
        }

        for (int i = 1; i <= n - 1; i++)dp[T][i] = inf;
        dp[T][n] = 0;

        for (int i = T - 1; i >= 0; i--)
            for (int j = 1; j <= n; j++) {
                dp[i][j] = dp[i + 1][j] + 1;//时间先前流动一秒
                if (j < n&&has_train[i][j][0] && i + t[j] <= T)
                    dp[i][j] = min(dp[i][j], dp[i + t[j]][j + 1]);
                if (j > 1 && has_train[i][j][1] && i + t[j - 1] <= T)
                    dp[i][j] = min(dp[i][j], dp[i + t[j - 1]][j - 1]);
            }

        printf("Case Number %d: ", tmp++);
        if (dp[0][1] >= inf) printf("impossible\n");
        else printf("%d\n", dp[0][1]);
    }
    return 0;
}

Uva437
题意:有n种正方体,每种都有无穷多个。要求选一些立方体摞成一根尽量高的柱子,使得每个立方体的地面长宽严格小于它下方立方体的地面长宽

解法:见代码

/*
思路:
因为a和b的值会很大,所以我们不能直接用d(a,b)来表示状态值;
所以我们(idx,h)来表示这个状态,h存放的是立方体的的三条高,知道其中的一条我们就能知道对应的面
idx表示立方体的编号
ans就是我们要求的那个结果,即最大高度,状态转移就是简单的拿或者不拿那个立方体
*/

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 30 + 5;
int n, blocks[maxn][3], d[maxn][3];

void get_dimensions(int* v, int b, int dim) {
    int idx = 0;
    for (int i = 0; i < 3; i++) {
        if (i != dim) v[idx++] = blocks[b][i];
    }
}

int dp(int i,int j) {
    int&ans = d[i][j];
    if (ans > 0)return ans;
    ans = 0;
    int v[2], v2[2];
    get_dimensions(v, i, j);
    for(int a=0;a<n;a++)
    for(int b=0;b<3;b++){//a和b是底面的边长
        get_dimensions(v2, a, b);
        if (v2[0] < v[0] && v2[1] < v[1]) ans = max(ans, dp(a, b));
    }
    ans += blocks[i][j];
    return ans;
}

int main() {
    int kase = 0;
    while (scanf("%d", &n) && n) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < 3; j++) 
                scanf("%d", &blocks[i][j]);
            sort(blocks[i], blocks[i] + 3);
        }
        memset(d, 0, sizeof(d));
        int ans = -1;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < 3; j++) {
                ans = max(ans, dp(i, j));
            }
        }
        printf("Case %d: maximum height = %d\n", ++kase, ans);
    }
    return 0;
 }

Uva116
题意:有一个m行n列的整数矩阵,从第一列任何一个位置出发每次往↗↘或者→走一格,最终到达最后一列,要求经过的整数之和最小。整个矩形是环形的,即第一行的上一行是最后一行,最后一行的下一行是第一行。输出路径上每列的行号。多解释输出字典序最小的

解法:见代码

/*
多阶段决策问题,重点是行和列的处理,注意看一下
还有一个重点是结点路径的保存
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf = 0x3fffffff;
int a[1010][1010], d[1010][1010], nex[1010][1010];

int main() {
    int n, m;
    while (scanf("%d%d", &m, &n) ==2 && m ) {//m行,n列
        memset(a, 0, sizeof(a)); memset(d, 0, sizeof(d));
        memset(nex, 0, sizeof(nex));

        for (int i = 0; i < m; i++) 
            for (int j = 0; j < n; j++) {
                scanf("%d", &a[i][j]);
            }

        int ans = inf, first = 0;
        for (int j = n - 1; j >= 0; j--) {//从每一行开始
            for (int i = 0; i < m; i++) {//从每一列开始
                if (j == n - 1)d[i][j] = a[i][j];//设置初始值
                else {
                    int row[3] = { i,i - 1,i + 1 };//三个方向
                    if (i == 0)row[1] = m - 1;//上一行是下边界
                    if (i == m - 1)row[2] = 0;//下一行是上边界
                    sort(row, row + 3);
                    d[i][j] = inf;
                    for (int k = 0; k < 3; k++) {
                        int v = d[row[k]][j + 1] + a[i][j];
                        //↓↓↓nex[i][j]表示从(i,j)出发的下一个坐标
                        if (v < d[i][j]) { d[i][j] = v; nex[i][j] = row[k]; }
                    }
                }
                if (j == 0 && d[i][j] < ans) { ans = d[i][j]; first = i; }//更新出发点
            }
        }

        printf("%d", first + 1);
        for (int i = nex[first][0], j = 1; j < n; i = nex[i][j], j++) {
            printf(" %d", i + 1);
        }printf("\n%d\n", ans);
    }
    return 0;
}

Uva12563
题意:有n首歌,剩余时间为UP,每首歌都有一个时间长度,要求你在剩余时间内尽可能多的唱歌,同时要求唱的时间尽可能的长

解法:01背包,双状态

/*
背包问题的变式,这道题的解法涉及到了一个dp中核心的解法:状态
注意枚举上界UP要-1
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 180 * 50 + 5;
struct node {
    int num, len;
    bool operator <  (const node& rhs) const {
        return num < rhs.num || (num == rhs.num&&len < rhs.len);
    }
};
node dp[maxn];
ll t[maxn];

int main() {
    int T; scanf("%d", &T);
    int kase = 1;
    while (T--) {
        ll n, UP; scanf("%lld%lld", &n, &UP);
        memset(t, 0, sizeof(t)); memset(dp, 0, sizeof(dp));
        ll sum = 0;
        for (int i = 1; i <= n; i++) scanf("%lld", &t[i]), sum += t[i];
        UP -= 1;

        for (int i = 1; i <= n; i++) {
            for (int j = UP; j >= 0; j--) {
                if (j < t[i])break;//剩余时间都不够t[i]了,没必要再选择了
                node tmp = { dp[j - t[i]].num + 1,dp[j - t[i]].len + t[i] };
                dp[j] = max(dp[j], tmp);//选取更优状态
            }
        }

        printf("Case %d: %d %d\n", kase++, dp[UP].num + 1, dp[UP].len + 678);
    }
    return 0;
}

Uva1625
题意:有两个字符串,每次可以选两个首位的字符将他们加入到新串的尾部,对于每个颜色来说(每个颜色就是每个字符),其跨度L(c)等于最大位置和最小位置之差。找到一种合并方式,使得所有的L(c)总和最小

解法:见代码

*
非常好的思路题,多多品味
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 5e3 + 5;
const int inf = 0x3fffffff;
int dp[maxn][maxn], cnt[maxn][maxn];
int bf[100], bs[100], ef[100], es[100];
char f[maxn], s[maxn];
//dp[i][j]为从第一个颜色序列中拿走前i个颜色,从第二个颜色中拿走前j个颜色时合并产生的序列的最小lc之和
//cnt[i][j]为分别取走i个颜色和j个颜色时还有多少种颜色已经出现但尚未结束
//bf[c]为f串中c字母出现的第一个位置,es[c]为f串中c字母出现的最后一个位置
//ef同上,只不过对应s串中的情况

int main() {
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%s%s", f + 1, s + 1);
        int lenf = strlen(f+1), lens = strlen(s+1);
        for (char c = 'A'; c <= 'Z'; c++) {
            bf[c] = bs[c] = inf;
            ef[c] = es[c] = 0;
        }

        //处理开始值和终点值,注意处理开始值的时候采用最值的方法可以避免定义first变量
        for (int i = 1; i <= lenf; i++) {
            char c = f[i];
            bf[c] = min(i, bf[c]);
            ef[c] = i;
        }
        for (int i = 1; i <= lens; i++) {
            char c = s[i];
            bs[c] = min(i, bs[c]);
            es[c] = i;
        }

        //处理cnt数组
        for (int i = 0; i <= lenf; i++) 
            for (int j = 0; j <= lens; j++) {
                if (i) {
                    cnt[i][j] = cnt[i - 1][j];//取走f数组中的一个颜色
                    char c = f[i];
                    if (bf[c] == i&&bs[c] > j)cnt[i][j]++;//f中已经出现但是s中没有出现
                    if (ef[c] == i&&es[c] <= j)cnt[i][j]--;//f中已经结束或者s中已经结束
                    //上面这个还值得推敲
                }
                if (j) {
                    cnt[i][j] = cnt[i][j - 1];
                    char c = s[j];
                    if (bs[c] == j&&bf[c] > i)cnt[i][j]++;
                    if (es[c] == j&&ef[c] <= i)cnt[i][j]--;
                }
            }

        for (int i = 0; i <= lenf; i++) 
            for (int j = 0; j <= lens; j++) {
                if (!i && !j)continue;
                dp[i][j] = inf;
                //这点非常重要,+cnt[i][j]其实将所有的字母的情况都考虑进去了
                if (i)dp[i][j] = min(dp[i][j], dp[i - 1][j] + cnt[i - 1][j]);
                if (j)dp[i][j] = min(dp[i][j], dp[i][j - 1] + cnt[i][j - 1]);
            }
        printf("%d\n", dp[lenf][lens]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/romaLzhih/p/9489797.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值