NOIP 2014(COGS 1805) 飞扬的小鸟 完全背包

AC之后发现这道题并不难。之前是各种不理解。而且写这道题之前我对完全背包的了解仅仅是转换成多重背包来做。这道题也算得上是第一次以正确的姿势O(nm)写完全背包。

首先可以确定可以用DP的思想来解这道题,无后效性,最优子结构。
一个不过的状态表示就是f[i][j]表示要到达(i,j)这个点最少点击屏幕的次数,由此也很容易想到一个转移:枚举上一个横坐标上点击屏幕次数f[i][j] = min{f[i-1][j-x[i-1]*k] + k},以及不点:f[i][j] = min(f[i][j], f[i-1][j+y[i-1]])。可惜这个是O(nm^2),会T。

然而我也是十分的佩服能在考场上想出完全背包正解的神犇们。。搁我在下面想好几天也想不出来。或者也是仅仅从这个转移上做了些优化,发现是完全背包。

思考完全背包做法:状态不变,或者说变一种说法:f[i][j]表示到底第i个横坐标(物品),纵坐标到达j(容量)的最少点击次数(最小价值)。完全背包的正确做法就是f[i][j]可以由f[i-1][j-k]转移而来同时也可以由f[i][j-k]转移而来,正序枚举容量,就可以保证是同一个物品会从自己转移,就是多次选到,在滚动数组中理解起来或许会更简单,写起来仅仅需要把0-1背包中的j改成正序枚举即可。

那么对于这道题,就是f[i][j] = min(f[i-1][j-x[i-1], f[i][j-x[i-1])+1, f[i][j] = min(f[i][j], f[i-1][j+y[i-1]])。
考虑它的道理:当f[i][j]由f[i][j-x[i-1]]转移得到时,f[i][j-x[i-1]]是由谁转移得到?有可能是f[i-1][j-x[i-1]-x[i-1]],那么就对应在i-1横坐标上点击两次;如果是f[i][j-x[i-1]-x[i-1]],同理。可以说是,我们不枚举k,只是把f[i-1][j-x[i-1]*k]保存到了f[i][j-x[i-1]*(k-1)]中去,我们时刻只需要k等于1,需要多次时,直接从f[i][j’]中转移。这样就省去了一层循环。

然而这就是完全背包O(nm)的正解。不过需要注意略恶心的边界处理以及不合法状态(碰壁)。对于不合法状态,我的做法是对于每个i先把1~m都计算出来,之后再把不合法的去掉,设为正无穷。这么做的好处就是代码看起来比较简洁,少了一些比较恶心的判断,少了一些容易出细节问题的地方,但是需要把f数组j的大小开到2000,否则在f[i][j] <– f[i-1][j+y[i-1]]时会出错。另一个边界就是当j==m时,它可以由很多状态转移过来,所以得单独给它写个循环来求。

我这份代码中,枚举i之下有6个for:
1.由i-1点击屏幕上升转移到i
2.单独枚举m,它可以由很多地方转移到
3.由i-1不点击屏幕下降转移到i
4、5.删去不合法状态
6.判断如果在横坐标i谁有可以到达的地方,那么可通过的管道数++

#include <cstdio>
#include <cstring>
#include <algorithm>
#define M 10005
using namespace std;

int n, m, k, cnt, f[M][2005];
int h[M], l[M], x[M], y[M];

int main()
{
    memset(h, 0x3f, sizeof h);
    memset(f, 0x3f, sizeof f);
    scanf("%d %d %d", &n, &m, &k);
    for(int i = 0; i < n; i++){
        scanf("%d %d", x+i, y+i);
    }
    for(int i = 1; i <= k; i++){
        int p;
        scanf("%d", &p);
        scanf("%d %d", l+p, h+p);
    }
    for(int i = 1; i <= m; i++){
        f[0][i] = 0;
    }

    for(int i = 1; i <= n; i++){
        for(int j = x[i-1]+1; j <= m; j++){
            f[i][j] = min(f[i-1][j-x[i-1]]+1, f[i][j-x[i-1]]+1);
        }
        for(int j = m-x[i-1]+1; j <= m; j++){
            f[i][m] = min(f[i-1][j]+1, f[i][m]);
            f[i][m] = min(f[i][j]+1, f[i][m]);
        }
        for(int j = 1; j <= m; j++){
            f[i][j] = min(f[i][j], f[i-1][j+y[i-1]]);
        }
        for(int j = l[i]; j; j--){
            f[i][j] = 1<<30;
        }
        for(int j = h[i]; j <= m; j++){
            f[i][j] = 1<<30;
        }
        for(int j = 1; j <= m && (h[i] <= m || l[i]); j++){
            if(f[i][j] < 1e7){
                cnt++;
                break;
            }   
        }
    }

    int ans = 1<<30;
    for(int i = 1; i <= m; i++){
        ans = min(ans, f[n][i]);
    }
    if(ans < 1e7) printf("1\n%d", ans);
    else printf("0\n%d", cnt);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值