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;
}
posted @ 2019-07-22 10:53  psimonw  阅读(371)  评论(0编辑  收藏  举报