BZOJ 2059 [Usaco2010 Nov]Feed 购买饲料

BZOJ_2059

    本来打算想想费用流的,但是既然给的是直线,八成就是让dp了,否则完全可以给成图的。

    不妨用f[i][j]表示递推到第i个商店时,在前i-1个商店一共买了j的饲料,这样就有状态转移方程f[i][j]=min{f[i-1][k]+(j-k)*C[i-1]}+j*j,其中0<=j-k<=F[i-1]。这样裸着做的话,复杂度是没法承受的,但如果把min{}里的j拿出来的话就会得到f[i][j]=min{f[i-1][k]-k*C[i-1]}+j*C[i-1]+j*j,这样min里面的只和k有关,而且k是要满足一定范围的,这时我们可以用单调队列来维护f[i-1][k]-k*C[i-1]的最小值,这样每次决策就是O(1)了。

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define MAXN 510
#define MAXK 10010
#define INF 0x3f3f3f3f3f3f3f3fll
typedef long long LL;
struct Shop
{
    int x, c, f;
    bool operator < (const Shop &t) const
    {
        return x < t.x;
    }
}shop[MAXN];
int N, E, K, q[MAXN];
LL f[MAXN][MAXK];
void init()
{
    for(int i = 0; i < N; i ++)
        scanf("%d%d%d", &shop[i].x, &shop[i].f, &shop[i].c);
    std::sort(shop, shop + N);
}
void solve()
{
    for(int i = 0; i <= N; i ++)
        for(int j = 0; j <= K; j ++) f[i][j] = INF;
    f[0][0] = 0;
    shop[N].x = E;
    for(int i = 1; i <= N; i ++)
    {
        int front = 0, rear = 0;
        for(int j = 0; j <= K; j ++)
        {
            while(front < rear && j - q[front] > shop[i - 1].f) ++ front;
            if(f[i - 1][j] != INF)
            {
                while(front < rear)
                {
                    int k = q[rear - 1];
                    if(f[i - 1][k] - (LL)k * shop[i - 1].c < f[i - 1][j] - (LL)j * shop[i - 1].c) break;
                    -- rear;
                }
                q[rear ++] = j;
            }
            if(front < rear)
            {
                int k = q[front];
                f[i][j] = f[i - 1][k] + (LL)(j - k) * shop[i - 1].c + (LL)j * j * (shop[i].x - shop[i - 1].x);
            }
        }
    }
    printf("%lld\n", f[N][K]);
}
int main()
{
    while(scanf("%d%d%d", &K, &E, &N) == 3)
    {
        init();
        solve();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/staginner/p/2719681.html