题解 P3161 【[CQOI2012]模拟工厂】

· · 题解

震惊!一道爆搜题竟然被评成了黑题!!!

咳咳。。。

没错,这道题就是一道爆搜题。。。

n <= 15,这让我们习惯性的想到了状压。。。

然后仔细想想,如果我们已经选好了要接受哪些订单,可不可以直接判断呢??

或者说更进一步,如果已经知道当前的生产力、剩了多少东西、接下来还有哪些订单,我们能不能求出在接下一个订单之前可以有多少时间提高生产力呢??

(这么多废话终于说完了。。。)

说了这么多显然是可以的嘛。。。

我们设可以有x单位时间来提高生产力,那么如果当前离下一个订单的时间为T时,这个订单要P个产品,工厂拥有M的生产力时,显然有如下方程:

(M + x) * (T - x) = P

整理后就可以得到:

x^2 + (M - T) * x + P - M * T = 0

利用根的判别式算一下有没有根,如果没有就说明无法接下这个订单,否则就可以拥有

\biggl \lfloor \frac{T - M + \sqrt{(M + T)^2 - 4P}}{2} \biggr \rfloor

的时间来增加生产力。

然后就这样枚举下去,每次如果方案可行就用这些订单的收入来更新答案。

下面贴代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 20;

struct P {
    int t; //订单的时间
    int c; //需要的产品数量
    int p; //得到的报酬

    friend bool operator< (P a, P b) { //按照时间排好序
        return a.t < b.t;
    }
}S[N], a[N];

int n;
int tot;
ll res;
// 设可以有x天提高生产力则可以列出方程: (Make + x) * (Time - x) = Need
// 整理得: x^2 + (Make - Time) * x + Need - Make * Time = 0
// 解方程即可
ll js(ll Make, ll Time, ll Need) { 
    ll a = 1;
    ll b = Make - Time;
    ll c = Need - Make * Time;
    ll derta = b * b - 4 * a * c;
    if(derta < 0) return -1;
    return floor((-b + sqrt(derta)) / 2 / a);
}
//检查当前枚举的方案可不可行,可行就更新答案
void solve(int Now) {
    tot = 0;
    ll num = 0;
    for(int i = 1; i <= n; i++)
        if(Now & (1 << i - 1))
            S[++tot] = a[i], num += a[i].p; //将当前状态的订单加入栈
    ll Make = 1; //生产力
    ll Have = 0; //剩余的产品数量
    for(int i = 1; i <= tot; i++) {
        ll t = S[i].t - S[i - 1].t; //在下一个订单前能够提高生产力的时间,最大就是这个
        ll sum = 0; //累加所需的产品
        for(int j = i; j <= tot; j++) {
            sum += S[j].c;
            if(sum > Have)
                t = min(t, js(Make, S[j].t - S[i - 1].t, sum - Have)); //用算出来的时间更新当前的时间
        }
        if(t < 0) return ; //说明不可行
        Make += t; //更新生产力
        Have += Make * (S[i].t - S[i - 1].t - t) - S[i].c; //更新库存
    }
    res = max(res, num);
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d%d%d", &a[i].t, &a[i].c, &a[i].p);
    sort(a + 1, a + 1 + n); //按照时间排好序
    for(int i = 0; i < (1 << n); i++)
        solve(i);
    printf("%lld\n", res);
    return 0;
}