loj534. 「LibreOJ Round #6」花团

题意

一个物品集合\(S\)初始为空,按时间递增顺序依次给出\(q\)次操作,操作如下:
1 v w e 表示在\(S\)中加入一个体积为\(v\)价值为\(w\)的物品,第\(e\)次操作结束之后移除该物品。
2 v 表示询问。你需要回答:
当前\(S\)是否存在一个子集使得子集中物品体积和为\(v\)
当前\(S\)的所有物品体积和为\(v\)的子集中,价值和最大是多少(空集的价值和为0)。
\(q \leq 15000, max_v \leq 15000\),强制在线。

题解

正解复杂度竟然是\(O(qv \log q)\)的!
既然是这个复杂度,那就可以乱胡一通(打脸.jpg)……
考虑这个问题虽然强制在线,但有个性质:插入的物品已经给出了删除时间。
所以就可以有类似线段树分治的做法:
加入一个物品时加入到线段树上做多\(O(\log q)\)个区间节点上(仅仅打个标记)。
在查询时直接向下暴力合并。
但是为了复杂度,我们在每个节点最多只能做一次合并标记的工作。
容易证明,如果某个节点在某个查询时,标记被合并过了,那么之后插入时,在这个节点到根的标记上,一定不会有新标记出现。
所以当一个节点做过合并标记了,就打个标记表示以后不会再合并这个节点(信息是可以直接用的了)。
合并的时候就是01背包,总时间复杂度是\(O(qv \log q)\)
但是还有问题在于空间。
这个问题有个巧妙的解决方案:记录深度,即开一个关于深度的dp数组。
因为在一个查询的时候,只需要\(O(\log q)\)个节点的信息,可以用深度直接进行区分;
并且线段树同一深度的每个节点信息使用的时间区间都是不交的(即对于任意一对时间区间\([l_1, r_1], [l_2, r_2]\),满足\(r_1 < l_2\)),不存在信息被同层节点占用后丢失,需要重新计算的问题。
空间复杂度降为\(O(v \log q)\)

#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair <int, int> pii;
const int L = 1 << 14, D = 18;

int q, maxv, T;
int f[D][L];
bool vis[L << 2];
pii Ans;
vector <pii> g[L << 2];

void insert (int o, int l, int r, int x, int y, int v, int w) {
    if (x <= l && r <= y) {
        g[o].push_back(mp(v, w));
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) {
        insert(o << 1, l, mid, x, y, v, w);
    }
    if (y > mid) {
        insert(o << 1 | 1, mid + 1, r, x, y, v, w);
    }
}
void append (int v, int w, int s, int t) {
    insert(1, 1, L, s, t, v, w);
}
pii query (int o, int l, int r, int x, int v, int d) {
    if (!vis[o]) {
        memcpy(f[d], f[d - 1], sizeof f[d]);
        for (auto p : g[o]) {
            for (int i = maxv; i >= p.fi; --i) {
                f[d][i] = max(f[d][i], f[d][i - p.fi] + p.se);
            }
        }
        vis[o] = 1;
    }
    if (l == r) {
        return mp(f[d][v] >= 0 ? 1 : 0, f[d][v] >= 0 ? f[d][v] : 0);
    }
    int mid = (l + r) >> 1;
    if (x <= mid) {
        return query(o << 1, l, mid, x, v, d + 1);
    } else {
        return query(o << 1 | 1, mid + 1, r, x, v, d + 1);
    }
}
pii ask (int x, int v) {
    return query(1, 1, L, x, v, 1);
}
int main () {
    memset(f[0], 192, sizeof f[0]), f[0][0] = 0;
    scanf("%d%d%d", &q, &maxv, &T);
    for (int qaq = 1, op, v, w, e, ans = 0; qaq <= q; ++qaq) {
        scanf("%d", &op), ans *= T;
        if (op == 1) {
            scanf("%d%d%d", &v, &w, &e);
            v -= ans, w -= ans, e -= ans;
            append(v, w, qaq, e);
        } else {
            scanf("%d", &v), v -= ans;
            Ans = ask(qaq, v);
            ans = Ans.fi ^ Ans.se;
            printf("%d %d\n", Ans.fi, Ans.se);
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/psimonw/p/11224545.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值