[USACO 2012 Open Gold] Bookshelf【优化dp】

传送门1:http://www.usaco.org/index.php?page=viewproblem2&cpid=138

传送门2:http://www.lydsy.com/JudgeOnline/problem.php?id=2678

最开始没看到要将那些书按顺序放!!一定是按顺序!我还以为是自己安排那个书架,白费了我好久时间。

然而看对题之后仍然不会。。。

令f(i)表示前i本书已经放到书架上,且第i本是某个书架的最后一本的最小高度和,则

f(i) = min { f(j) + max { h[j + 1], h[j + 2]....., h[i] } }, 其中w[i + 1] + w[i + 2] + ... + w[j] <= L.

裸的是O(N^2)的(好像Silver版本的这一题就是O(N^2)可以过),所以需要优化。(又是dp优化!每次看dp优化少说都要2个钟头,烦)

显然,f(j)随j的增大而增大(非严格增大,即大于等于),而对于不变的i,max { h[j + 1], h[j + 2]....., h[i] } 随j的增大而减小(非严格减小,即小于等于),因此我们可以把i前面的所有j,按照max { h[j + 1], h[j + 2]....., h[i] }的大小来分块,把这个值相等的j分到一块去,那么对于这一块j,显然取最前面那个最好,因为根据刚刚所说,f(j)随j的增大而增大(非严格增大,即大于等于)。然而有很多块,那么应该取那一块的最前面那个呢?我们应该用一个平衡树(multiset,就是STL set的可以重复元素版本)把所有可以取的值存起来,并在dp过程中不停地删除那些不能取的值,并添加新的可以取的值。具体见代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <cstdio>
#include <set>
 
const int maxn = 100005;
 
int n, ttttttttt[maxn], num, *tail = ttttttttt;
long long L, s[maxn], h[maxn], f[maxn];
std::multiset<long long> val;
 
int main(void) {
    freopen("bookshelf.in", "r", stdin);
    freopen("bookshelf.out", "w", stdout);
    scanf("%d%I64d", &n, &L);
    for (int i = 1; i <= n; ++i) {
        scanf("%I64d%I64d", h + i, s + i);
        s[i] += s[i - 1];
    }
     
    // num表示现在拥有块的个数, tail[i]表示第i个块的结尾位置,
    // 注意tail是一个指针,这是有方便之处的,之后就会看到
    for (int i = 1; i <= n; ++i) {
        // 下面是将第i本书考虑进去后,重新更新分块
        while (num && h[i] >= h[tail[num]]) {
            val.erase(val.find(f[tail[num - 1]] + h[tail[num]]));
            --num;
        }
        tail[++num] = i;
        val.insert(f[tail[num - 1]] + h[i]);
         
        // tail[0]应该表示的是合法的j使得前缀和s[i] - s[j] <= L
        // 当 > L时,就应该erase掉对应的值,然后++tail[0],但如果
        // 下一个就是这个块的结尾了,就要指针移动啦,因为这个块整
        // 个都被删除了,然后再--num
        while (s[i] - s[tail[0]] > L) {
            val.erase(val.find(f[tail[0]] + h[tail[1]]));
            if (tail[0] + 1 == tail[1]) {
                ++tail;
                --num;
            }
            else {
                val.insert(f[tail[0] + 1] + h[tail[1]]);
                ++tail[0];
            }
        }
         
        f[i] = *val.begin();
    }
    printf("%I64d\n", f[n]);
    return 0;
}

官方题解是记录了一个块有多大,我是记录了这个快的结尾位置,都一样。

posted @   ciao_sora  阅读(807)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示