sduacm16级寒假训练 动态规划(二)

题目链接:https://vjudge.net/contest/148894

动态规划(二)

uva 10626 Buying Coke

[tran]

在自动售货机上买n瓶可乐,你有面值为10、5、1的硬币n1、n2、n3个。要求可乐需要一瓶一瓶的买,每

次买完一瓶后机器找钱(机器找钱时采取硬币数最少的方案),询问最少投币数。

数据保证T<=20,n<=150,n1<=50,n2<=100,n3<=500

[sol]

每次买一瓶可乐作为阶段,而一个阶段的状态用两个维度去表示,即f[p,x,y]代表买了p瓶可乐,剩余x个5,

y个10的最小投币数,而每次买可乐有5种方案: 10;5 + 5;5 + 1 + 1 + 1;10 + 1 + 1 + 1;8*1

其实不是说这个人只有这四种投币可能,而是说其他方案都不优秀(多投的那些硬币还会还回来,别想着通

过机器可以把1换成10或5,可以思考,这样显然不优)特别注意投13找5的情况,特别容易漏掉

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int f[150 + 5][100 + 5][50 + 5];
int c, n1, n2, n3;
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        memset(f, 110, sizeof(f));
        scanf("%d%d%d%d", &c, &n1, &n2, &n3);
        f[0][n2][n3] = 0;
        int ans = 1e10;
        for (int p = 1; p <= c; p++)
            for(int x = 0; x <= 100; x++)
              for(int y = 0; y <= 50; y++)
              {
                  f[p][x][y] = min(min (min (f[p - 1][x][y + 1] + 1, f[p - 1][x + 2][y] + 2), f[p - 1][x + 1][y] + 4), f[p - 1][x][y] + 8);
                  if (x > 0)
                  f[p][x][y] = min(f[p][x][y], f[p - 1][x - 1][y + 1] + 4);
                  if (p == c)
                     ans = min (ans, f[c][x][y]);
              }
        printf("%d\n", ans);
    }
    return 0;
}

HDU 1500 Chopsticks

[tran]

有n个数据,给定k,要从中选出k+8个三元组(x,y,z,其中x<=y<=z),每选一次的代价为(x-y)^2,

求最小代价和。

[sol]

假设选取方案中有(x,y,z),存在t,使得y>t>x,显然(y - x) < (t - x),选取(x,t,z)可以优化代价,并且y相比t

更有可能作为其他组的第三个元素,这与最优方案矛盾,故假设不成立。所一肯定不存在这样的t,所以sort

一遍,每次选取相邻的x,y即可,但是z呢?选取的时候怎么保证有z可选呢?

如果不考虑z的话,sort后用O(n*k)的dp就可以搞出来答案,即f[i][j]代表前i个数选取j组的最小代价,那么

f[i][j]=min(f[i - 1][j], f[i - 2][j] + cost),其实仔细想想,如果sort倒着排序,那么对于f[i][j]来说,只要满

足i>=3*j就可以了,这样问题就迎刃而解了

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 1000 + 15;
const int M = 5000 + 5;
int f[M][N], a[M];
int n, m;
bool cmp(int a, int b)
{
    return (a > b);
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &n, &m);
        n += 8;
        for(int i = 1; i <= m; i++)
            scanf("%d", &a[i]);
        memset(f, 110, sizeof(f));
        sort(a + 1, a + m + 1, cmp);
        for(int i = 0; i <= m; i++)
            f[i][0] = 0;
        for (int i = 3; i <= m; i++)
            for (int j = 1; j <= i / 3; j++)
               f[i][j] = min(f[i - 1][j], f[i - 2][j - 1] + (a[i] - a[i - 1]) * (a[i] - a[i - 1]));
        printf("%d\n", f[m][n]);
    }
    return 0;
}

HDU 3853 LOOPS

[tran]

n*m的坐标系,每个坐标都有一次选择的机会(选择的代价是2),选择的方向是静止、向下一个单位,向

右一个单位,问从(1,1)到(n,m)的期望代价

[sol]

先吐槽一下期望题…,高中遇到的概率期望全部忽略掉,(我能怎么办啊,我也很绝望啊~~),网上盛传一

句话,概率题从前往后推,期望题从后往前推,原因是dp的初始化,对于概率题,起点的概率是1,是确定

的,而终点的概率位置,对于期望题目,从终点到终点的期望代价显然为0,所以要倒推

此题是很基础的期望题,f[i][j]代表从(i,j)到(n,m)的期望代价,那么f[i][j]=p1[i][j]f[i][j] +p2[i][j]

f[i ][j + 1] + p3[i][j] * f[i + 1][j] + 2,移项可得f[i][j] = (p2*f[i][j + 1]+p3*f[i+1][j] + 2)/(1 - p1)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int N = 1000 + 5;
const double eps = 1e-13;
double f[N][N], p1[N][N], p2[N][N], p3[N][N];
int n,m;
int main()
{
    while(scanf("%d%d", &n, &m) == 2)
    {
        for(int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
              scanf("%lf%lf%lf", &p1[i][j], &p2[i][j], &p3[i][j]);
        memset(f, 0, sizeof(f));
        for(int i = n; i > 0; i--)
            for (int j = m; j > 0; j--)
        {
            if (i == n && j == m)
                continue;
            if(fabs(p1[i][j] - 1) < eps)
                continue;
            f[i][j] = (f[i + 1][j] * p3[i][j] + f[i][j + 1] * p2[i][j] + 2)/(1 - p1[i][j]);
        }
        printf("%.3lf\n",f[1][1]);
    }
    return 0;
}

poj 1160 Post Office

[tran]

在x轴上有n(<300)个村庄,建设m个邮局(m<=n),每个村庄的代价是到其最近邮局的距离,询问n个村

庄的最小代价。(m<30)

[sol]

很经典的问题,但是这个dp不是特别好做(个人感觉,dalao请忽略这句话),首先考虑阶段,其实每个邮

局负责的村庄就是一个区间,这样每段区间就是一个阶段,f[i][j]代表i个村庄建j个邮局的min代价,其实i就

是第j段区间的最后一段,这样f[i][j]=minf[k][j - 1]+cost[k + 1][j],注意刚开始的时候又枚举cost,即这段

区间在哪建邮局花费最小,导致疯狂TLE,最好dp前预处理一下。

其实在(l,r)区间建一个邮局,这个邮局建在中点处最优(证明方法略)

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 300 +5;
const int M = 30 + 5;
int f[N][M], tot[N][N];
int a[N], sum[N];
int main()
{
    int n, m;
    while(~scanf("%d%d",&n,&m))
    {
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        sum[i] = sum[i - 1] + a[i];
    }
    memset(f, 110, sizeof(f));
    memset(tot, 110, sizeof(tot));
    f[0][0] = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= i - 1; j++)
        {
             for (int k = j + 1; k <= i; k++)
               tot[j + 1][i] = min (tot[j + 1][i], a[k] * (2 * k - i - j) + sum[i] + sum[j] - 2 *sum[k]);
             //printf("tot[%d,%d]=%d\n", j + 1, i, tot[j + 1][i]);
        }
    for (int i = 1; i <= n; i++)
        for (int p = 1; p <= min (i, m); p++)
    {
        for (int j = 0; j <= i - 1; j++)
        f[i][p]=min(f[i][p], f[j][p - 1] + tot[j + 1][i]);
      //  printf("f[%d,%d]=%d\n", i, p, f[i][p]);
    }
    printf("%d\n", f[n][m]);
    }
    return 0;
}

POJ 1141 Brackets Sequence

[tran]

给出一个由(、)、[、]组成的字符串,添加最少的括号使得所有的括号匹配,输出最后得到的字符串。

[sol]

区间dp,这个题需要输出修复的字符串,用到了一个数组来标记,即pos[i][j]来标记f[i][j]是通过哪种方式更

新而来,是i与j匹配得到,还是分成两个小区间,最后递归输出

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

const int N = 100 +8;
const int inf = 1e9;
int f[N][N], pos[N][N];
string s;
int n;
bool ok(int x,int y)
{
    return (s[x] == '(' && s[y] == ')') || (s[x] == '[' && s[y] == ']');
}
void pout(int l, int r)
{
    if (l > r)
        return ;
    if (l == r)
    {
        if (s[l] == '(' || s[l] == ')')
        printf("()"); else
         printf("[]");
        return ;
    }
    if (pos [l][r] == -1)
    {
        printf("%c", s[l]);
        pout(l + 1, r - 1);
        printf("%c", s[r]);
    } else
    {
        pout(l, pos[l][r]);
        pout(pos[l][r] + 1, r);
    }

}
int main()
{
    cin>>s;
    n = s.length();
    memset(f, 0, sizeof(f));
    for (int i = 0; i < n; i++)
        f[i][i] = 1;
    for (int p = 1; p < n; p++)
        for(int i = 0; i < n - p; i++)
    {
        int j = i + p;
        f[i][j] = inf;
        if (ok(i, j))
        {
            f[i][j] = f[i + 1][j - 1];
            pos[i][j] = -1;
        }
        for(int k = i; k < j; k++)
        {
            int tmp = f[i][k] + f[k + 1][j];
            if (tmp < f[i][j])
            {
                f[i][j] = tmp;
                pos[i][j] = k;
            }
        }
     //   printf("f[%d,%d]=%d\n", i, j, f[i][j]);
    }
 //   printf("%d\n",f[0][n - 1]);
    pout(0, n - 1);
    printf("\n");
    return 0;
}

总结

本套题质量很高,自己wa了好多发……(心累)。对于A题,转移的方式少了一种,浪费许多时间;第一次A掉期望题,好开心O(∩_∩)O~~;对于D、E题,自己当时直接打了一个比较暴力的dp,导致TLE,然后重新优化;希望自己:
1.先把转移方程列出来,检查转移方式是否缺少
2.计算复杂度(当时养成的习惯丢了,望重新养成)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值