[关闭]
@LinKArftc 2015-08-17T15:55:05.000000Z 字数 6497 阅读 2341

dp

动态规划


poj 1018 双关键字dp

题意

某公司要建立一套通信系统,该通信系统需要n种设备,而每种设备分别可以有m1、m2、m3、...、mn个厂家提供生产,而每个厂家生产的同种设备都会存在两个方面的差别:带宽bandwidths 和 价格prices。
现在每种设备都各需要1个,考虑到性价比问题,要求所挑选出来的n件设备,要使得B/P最大。
其中B为这n件设备的带宽的最小值,P为这n件设备的总价。

思路

我们定义状态 dp[i][j] 表示选择了前 i 个宽带其容量为 j 的最小费用。
很容易得到转移方程 : dp[i][j] = min(dp[i][j], dp[i-1][k] + p);
注意选择 j 的时候的大小情况。

AC代码

  1. int dp[105][1005];//前 i 个device在带宽为 j 时的最小价格
  2. int main() {
  3. int T;
  4. scanf("%d", &T);
  5. while (T --) {
  6. memset(dp, 0x3f, sizeof(dp));
  7. int n;
  8. scanf("%d", &n);
  9. for (int i = 1; i <= n; i ++) {
  10. int mi;
  11. scanf("%d", &mi);
  12. for (int j = 1; j <= mi; j ++) {
  13. int b, p;
  14. scanf("%d %d", &b, &p);
  15. if (i == 1) dp[1][b] = min(dp[1][b], p);
  16. else {
  17. for (int k = 0; k < 1005; k ++) {
  18. if (dp[i-1][k] < inf) {
  19. if (k <= b) dp[i][k] = min(dp[i][k], dp[i-1][k] + p);
  20. else dp[i][b] = min(dp[i][b], dp[i-1][k] + p);
  21. }
  22. }
  23. }
  24. }
  25. }
  26. double ans = 0.0;
  27. for (int i = 0; i < 1005; i ++) {
  28. if (dp[n][i] < inf) {
  29. ans = max(ans, (double)i / dp[n][i]);
  30. }
  31. }
  32. printf("%.3f\n", ans);
  33. }
  34. return 0;
  35. }

POJ 1050 最大子矩阵和

题意

求最大子矩阵和 (n <= 100)

思路

将二维问题转化为一维,对于第 k 列,暴力求第 i 行到第 j 行的和,这样就转化为一维数组上求最大子段和。
复杂度 O(n^3)

AC代码

  1. const int maxn = 105;
  2. int arr[maxn][maxn];
  3. int sum[maxn];
  4. int n;
  5. int max_arr() {
  6. int ret = 0;
  7. int cur = 0;
  8. for (int i = 1; i <= n; i ++) {
  9. cur += sum[i];
  10. if (cur > 0) ret = max(ret, cur);
  11. else cur = 0;
  12. }
  13. return ret;
  14. }
  15. int main() {
  16. while (~scanf("%d", &n)) {
  17. for (int i = 1; i <= n; i ++) {
  18. for (int j = 1;j <= n; j ++) {
  19. scanf("%d", &arr[i][j]);
  20. }
  21. }
  22. int ans = 0;
  23. for (int i = 1; i <= n; i ++) {
  24. memset(sum, 0, sizeof(sum));
  25. for (int j = i; j <= n; j ++) { //i -> j row
  26. for (int k = 1; k <= n; k ++) sum[k] += arr[j][k];
  27. int cur = max_arr();
  28. ans = max(ans, cur);
  29. }
  30. }
  31. printf("%d\n", ans);
  32. }
  33. return 0;
  34. }

POJ 1088 滑雪

题意

经典的滑雪问题,连题号我都记住了= =,没事写来练练手

思路

记忆话搜索

AC代码

  1. const int maxn = 105;
  2. int n, m;
  3. int mp[maxn][maxn];
  4. int dp[maxn][maxn];
  5. int dx[] = { -1, 0, 1 , 0 };
  6. int dy[] = { 0, 1, 0, -1 };
  7. int search(int x, int y) {
  8. if (dp[x][y]) return dp[x][y];
  9. for (int i = 0; i < 4; i ++) {
  10. int xx = x + dx[i];
  11. int yy = y + dy[i];
  12. if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && mp[xx][yy] > mp[x][y]) {
  13. int cur = search(xx, yy);
  14. dp[x][y] = max(dp[x][y], cur + 1);
  15. }
  16. }
  17. return dp[x][y];
  18. }
  19. int main() {
  20. while (~scanf("%d %d", &n, &m)) {
  21. for (int i = 1; i <= n; i ++) {
  22. for (int j = 1; j <= m; j ++) {
  23. scanf("%d", &mp[i][j]);
  24. }
  25. }
  26. memset(dp, 0, sizeof(dp));
  27. int ans = 0;
  28. for (int i = 1; i <= n; i ++) {
  29. for (int j = 1; j <= m; j ++) {
  30. int cur = search(i, j);
  31. ans = max(ans, cur);
  32. }
  33. }
  34. printf("%d\n", ans + 1);
  35. }
  36. return 0;
  37. }

POJ 1189 概率dp

思路

写的第一道概率dp。
第i层有2*i+1种可能的位置(从第0个空位到最后一个空位总共i+1个和i个钉子位置),注意题目中下标从0开,用d[i][j]表示第i行在第j个位置掉落的概率的分子(分母是2^i)。

如果位置是空位,那么有3种情况:

  1. 从上一个同样的位置掉落下来
  2. 掉落到左边的钉子(如果有)并向右走
  3. 掉落到右边的钉子(如果有)并向左走。

如果位置是有钉子的,有2种情况:

  1. 这个位置有钉子,那么不可能以这个位置掉落
  2. 这个位置没有钉子,可以从上一个同样的位置掉落下来。

AC代码

  1. int mp[110][110];
  2. ll dp[110][210];
  3. int n, m;
  4. int main() {
  5. scanf("%d %d", &n, &m);
  6. char tmp;
  7. for (int i = 1; i <= n; i ++) {
  8. for (int j = 1; j <= i; j ++) {
  9. scanf(" %c", &tmp);
  10. if (tmp == '*') mp[i][j] = 1;
  11. else mp[i][j] = 0;
  12. }
  13. }
  14. if (mp[1][1] == 1) {
  15. dp[1][1] = 1;
  16. dp[1][2] = 0;
  17. dp[1][3] = 1;
  18. } else {
  19. dp[1][1] = 0;
  20. dp[1][2] = 2;
  21. dp[1][3] = 0;
  22. }
  23. for (int i = 2; i <= n; i ++) {
  24. dp[i][1] = mp[i][1] == 1 ? dp[i-1][1] : 0;
  25. dp[i][2*i+1] = mp[i][i] == 1 ? dp[i-1][2*i-1] : 0;
  26. for (int j = 2; j <= 2*i ; j ++) {
  27. ll cur;
  28. if (j % 2) {
  29. cur = dp[i-1][j-1] * 2;
  30. cur += mp[i][j/2] == 1 ? dp[i-1][j-2] : 0;
  31. cur += mp[i][j/2+1] == 1 ? dp[i-1][j] : 0;
  32. } else {
  33. cur = mp[i][j/2] == 1 ? 0 : dp[i-1][j-1] * 2;
  34. }
  35. dp[i][j] = cur;
  36. }
  37. }
  38. ll a = dp[n][2*m+1];
  39. ll b = (ll)1 << n;
  40. while ((a % 2 == 0) && (b % 2 == 0)) {
  41. a /= 2;
  42. b /= 2;
  43. }
  44. printf("%lld/%lld\n", a, b);
  45. return 0;
  46. }

POJ 1322 概率dp

题意

一共有c种巧克力, 每种的个数都是一样多并且是足够多,现在从包里面拿出n次巧克力一次一个,当桌面上有2个相同的时候就吃掉这两个, 现在问你在拿出n次之后桌面上恰好有m个巧克力的概率.

思路

状态dp[i][j]: 第i次操作后, 桌面上出现j个巧克力的概率.因为每次吃两个,所以(i+j)为奇数是dp[i][j]=0。
状态转移:dp[i][j] = dp[i-1][j-1] * (c-j+1.0) / c + dp[i-1][j+1] * (j+1.0) / c;第i次只与第i-1次有关,所以空间可以压缩。
另外,由于C (C <= 100), N and M (N, M <= 1000000),所以 O(C*N) 华丽丽的超时了,看网上的代码才知道因为精度是三位小数,所以当拿的次数大于1000时概率已经是趋于平衡。。= =||,万万没想到~~~

AC代码

  1. const int maxn = 105;
  2. int c, n, m;
  3. double dp[2][maxn];
  4. int main() {
  5. while (~scanf("%d", &c) && c) {
  6. scanf("%d %d", &n, &m);
  7. if (m > c || m > n || ((m+n)&1)) {
  8. printf("0.000\n");
  9. continue;
  10. }
  11. memset(dp, 0, sizeof(dp));
  12. dp[0][0] = 1;
  13. if (n > 10000) n = 10000 + (n&1);
  14. for (int i = 1; i <= n; i ++) {
  15. for (int j = 0; j <= c; j ++) {
  16. if ((i+j)&1) continue;
  17. dp[i&1][j] = dp[(i-1)&1][j-1] * (c-j+1.0) / c + dp[(i-1)&1][j+1] * (j+1.0) / c;
  18. }
  19. }
  20. printf("%.3f\n", dp[n&1][m]);
  21. }
  22. return 0;
  23. }

POJ 1609 双关键字dp

题意

n 个木板,每个木板有l,m两个属性,当a木板的l和m都大于等于b木板时,a可以放在b上面,问最多可以堆叠多少个木板

思路

num[i][j] 表示l=i,m=j的木板数目, dp[i][j] 表示l=i,m=j时最多可以堆叠的木板数目,则dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + num[i][j];

AC代码

  1. //把多余的头文件、注释和num数组去掉之后仍然有260K,不知道那些44K的大神是怎么写的= =
  2. const int maxn = 105;
  3. int n;
  4. int dp[maxn][maxn];
  5. int num[maxn][maxn];
  6. int main() {
  7. while (~scanf("%d", &n) && n) {
  8. int a, b;
  9. memset(num, 0, sizeof(num));
  10. memset(dp, 0, sizeof(dp));
  11. for (int i = 1; i <= n; i ++) {
  12. scanf("%d %d", &a, &b);
  13. num[a][b] ++;
  14. }
  15. for (int i = 1; i <= 100; i ++) {
  16. for (int j = 1; j <= 100; j ++) {
  17. dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + num[i][j];
  18. }
  19. }
  20. printf("%d\n", dp[100][100]);
  21. }
  22. printf("*\n");
  23. return 0;
  24. }

POJ 1644 概率dp

题意

一个线性棋盘,一开始玩家在0位置,重点位置为m+1,每一轮抛硬币决定向右一步还是两步,概率各为0.5。棋盘每格上都会有一个说明+n,-n,L,或者0,分别意味着向右走n格,向左走n格,失去一轮机会,无任何说明。给出棋盘的大小,以及棋盘上每一格的说明,求在第t轮或者少于t轮便走到终点处的概率。概率恰好等于0.5,printf("Push. 0.5000\n");,大于0.5,printf("Bet for. %.4f\n", ans);,小于0.5,printf("Bet against. %.4f\n", ans);

思路

不难,dp[i][j]表示第i次走到j位置的概率

AC代码

  1. #define eps 1e-8
  2. const int inf = 0x3f3f3f3f;
  3. const int maxn = 60;
  4. int ins[maxn];
  5. double dp[maxn][maxn]; //第i次走到j位置的概率
  6. char str[5];
  7. int m, t;
  8. int main() {
  9. int T;
  10. scanf("%d", &T);
  11. while (T --) {
  12. scanf("%d %d", &m, &t);
  13. ins[0] = 0;
  14. ins[m + 1] = 0;
  15. ins[m + 2] = -1;
  16. for (int i = 1; i <= m; i ++) {
  17. scanf("%s", str);
  18. if (str[0] == 'L') ins[i] = inf;
  19. else sscanf(str, "%d", &ins[i]);
  20. }
  21. memset(dp, 0, sizeof(dp));
  22. dp[0][0] = 1.0;
  23. for (int i = 0; i <= t; i ++) {
  24. for (int j = 0; j <=m; j ++) {
  25. if (ins[j+1] == inf) dp[i+2][j+1] += dp[i][j] * 0.5;
  26. else dp[i+1][j+1+ins[j+1]] += dp[i][j] * 0.5;
  27. if (ins[j+2] == inf) dp[i+2][j+2] += dp[i][j] * 0.5;
  28. else dp[i+1][j+2+ins[j+2]] += dp[i][j] * 0.5;
  29. }
  30. }
  31. double ans = 0.0;
  32. for (int i = 0; i <= t; i ++) ans += dp[i][m+1];
  33. if (fabs(ans - 0.5) < eps) printf("Push. 0.5000\n");
  34. else if (ans > 0.5) printf("Bet for. %.4f\n", ans);
  35. else printf("Bet against. %.4f\n", ans);
  36. }
  37. return 0;
  38. }

POJ 2385

题意

有两棵苹果树,标号分别为1,2。每分钟其中一棵树会掉下一个苹果,但不知道是哪棵,奶牛初始位置为1,从一个树跑到另一个树需要一分钟,但它最多只会跑W次。问你在T分钟内,奶牛最多能接到多少苹果。

思路

刚开始是想用三维 dp[i][j][k]表示奶牛第i分钟跑了j次在位置k最多能接到的苹果数,后来突然发现既然奶牛初始位置为1且只有两棵树,那j为奇数奶牛位置肯定为2,为偶数肯定为1,所以二维dp就可以了,dp[i][j]表示奶牛第i分钟跑了j次最多能接到的苹果数,所以j > 0时,用cur表示当前是哪颗苹果树掉苹果,则dp[i][j] = max(dp[i-1][j], dp[i-1][j-1[) + cnt; 如果j为奇数cnt = (cur == 2 ? 1 : 0);,否则cnt = (cur == 1 ? 1 : 0);

AC代码

  1. const int maxt = 1005;
  2. const int maxw = 35;
  3. int dp[maxt][maxw];
  4. int pos[maxt];
  5. int main() {
  6. int t, w, cur;
  7. scanf("%d %d", &t, &w);
  8. memset(dp, 0, sizeof(dp));
  9. for (int i = 1; i <= t; i ++) {
  10. scanf("%d", &cur);
  11. for (int j = 0; j <= w; j ++) {
  12. if (j) dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]);
  13. else dp[i][j] = dp[i-1][j];
  14. if (j & 1) dp[i][j] += (cur == 2 ? 1 : 0);
  15. else dp[i][j] += (cur == 1 ? 1 : 0);
  16. }
  17. }
  18. int ans = -1;
  19. for (int j = 1; j <= w; j ++) ans = max(ans, dp[t][j]);
  20. printf("%d\n", ans);
  21. return 0;
  22. }

POJ 2181

题意

n(1 <= n <= 150000) 个数组成的序列,sum初始值为0,求它的一个子序列,sum加上这个序列的每个奇数项,减去这个序列的每个偶数项,使得sum最大。

思路

dp[i][0]表示前i个数,总共选了偶数次得到的最大sum
dp[i][1]表示前i个数,总共选了奇数次得到的最大sum
所以:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - num[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + num[i]);

AC代码

  1. const int maxn = 150005;
  2. int dp[maxn][2];
  3. int num[maxn];
  4. int main() {
  5. int n;
  6. while (~scanf("%d", &n)) {
  7. dp[0][0] = dp[0][1] = 0;
  8. for (int i = 1; i <= n; i ++) scanf("%d", &num[i]);
  9. for (int i = 1; i <= n; i ++) {
  10. dp[i][0] = max(dp[i-1][0], dp[i-1][1] - num[i]);
  11. dp[i][1] = max(dp[i-1][1], dp[i-1][0] + num[i]);
  12. }
  13. printf("%d\n", max(dp[n][0], dp[n][1]));
  14. }
  15. return 0;
  16. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注