题目链接: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.计算复杂度(当时养成的习惯丢了,望重新养成)