zoukankan      html  css  js  c++  java
  • 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
    ", Ans.fi, Ans.se);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    C#创建https请求并使用pfx证书 拓荒者
    "类型初始值设定项引发异常" 解决方法 拓荒者
    Web乱码解决方法 拓荒者
    Asp.net Ajax Accordion控件的用法 拓荒者
    【转】最强日期正则表达式 拓荒者
    【转】Http之Get/Post请求区别 拓荒者
    【转】Log4Net五步走 拓荒者
    SqlServer 错误:"SQL Server 无法生成 FRunCM 线程" 解决办法 拓荒者
    log4net用法实例 拓荒者
    Asp.net Ajax Calendar控件用法 拓荒者
  • 原文地址:https://www.cnblogs.com/psimonw/p/11224545.html
Copyright © 2011-2022 走看看