zoukankan      html  css  js  c++  java
  • [题解] [CF500F] New Year Shopping

    题面

    题解

    提供两种方法

    线段树分治

    将一个物品可以购买的时间区间打到线段树上
    考虑对于每一个点如何算贡献
    从线段树的根开始做 01 背包
    向下递归时记得撤销不同区间的影响
    这样每一次询问只会算 (log(t)) 次, 每一个物品, 只会在 (log(t)) 段区间中被计算
    每次计算的复杂度是 (O(max(b)))
    所以总的复杂度就是 (O(n*log(t)*b)), 可以通过这道题

    Code1

    
    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <vector>
    const int N = 2e4 + 5; 
    const int INF = 0x3f3f3f3f; 
    #define pii pair<int, int> 
    using namespace std; 
    
    int n, p, c[N], h[N], t[N], m, f[N], ans[N], lim; 
    vector<pii > vec1[N * 16], vec2[N * 16]; 
    
    template < typename T >
    inline T read()
    {
    	T x = 0, w = 1; char c = getchar(); 
    	while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
    	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 
    	return x * w; 
    }
    
    void insert(int p, int l, int r, int ql, int qr, int c, int v)
    {
    	if(ql <= l && r <= qr)
    		return (void) (vec1[p].push_back(make_pair(c, v))); 
    	int mid = (l + r) >> 1; 
    	if(ql <= mid) insert(p << 1, l, mid, ql, qr, c, v); 
    	if(mid < qr) insert(p << 1 | 1, mid + 1, r, ql, qr, c, v); 
    }
    
    void modify(int p, int l, int r, int k, int sz, int x)
    {
    	vec2[p].push_back(make_pair(x, sz)); 
    	if(l == r) return; 
    	int mid = (l + r) >> 1; 
    	if(k <= mid) modify(p << 1, l, mid, k, sz, x); 
    	else modify(p << 1 | 1, mid + 1, r, k, sz, x); 
    }
    
    void solve(int p, int l, int r)
    {
    	int sz = vec1[p].size(); 
    	for(int c, v, i = 0; i < sz; i++)
    	{
    		c = vec1[p][i].first, v = vec1[p][i].second; 
    		for(int j = 4000; j >= c; j--)
    			f[j] = max(f[j], f[j - c] + v); 
    	}
    	for(int i = 1; i <= 4000; i++)
    		f[i] = max(f[i], f[i - 1]); 
    	sz = vec2[p].size(); 
    	for(int tmp, i = 0; i < sz; i++)
    		ans[tmp = vec2[p][i].first] = max(ans[tmp], f[vec2[p][i].second]); 
    	if(l == r) return; 
    	int mid = (l + r) >> 1, g[4000]; 
    	for(int i = 0; i <= 4000; i++)
    		g[i] = f[i]; 
    	solve(p << 1, l, mid); 
    	for(int i = 0; i <= 4000; i++)
    		f[i] = g[i]; 
    	solve(p << 1 | 1, mid + 1, r); 
    	for(int i = 0; i <= 4000; i++)
    		f[i] = g[i]; 
    }
    
    int main()
    {
    	n = read <int> (), p = read <int> (); 
    	for(int i = 1; i <= n; i++)
    		c[i] = read <int> (), h[i] = read <int> (), t[i] = read <int> (), lim = max(lim, t[i]); 
    	lim = lim + p - 1; 
    	for(int i = 1; i <= n; i++)
    		insert(1, 1, lim, t[i], t[i] + p - 1, c[i], h[i]); 
    	m = read <int> (); 
    	for(int a, b, i = 1; i <= m; i++)
    	{
    		a = read <int> (), b = read <int> (); 
    		if(a > lim) { ans[i] = 0; continue; }
    		modify(1, 1, lim, a, b, i); 
    	}
    	solve(1, 1, lim); 
    	for(int i = 1; i <= m; i++)
    		printf("%d
    ", ans[i]); 
    	return 0; 
    }
    

    将序列分块之后背包

    不妨将题目意思转化, 考虑到每一个物品存在的时间区间长度都是一样的
    那对于一次询问 ((a, b)) 只有最早出现的时间在 ([a - p, a]) 中的物品才会贡献这次询问
    考虑将序列分为一个个大小为 (p) 的块, 总共有 (frac{max(t)}{p}) 个块
    将所有块的端点叫做关键点
    那么一次询问查询的区间包含且仅包含一个关键点
    且只有这个关键点左边的块中的一个后缀和右边的块中的一个前缀才能贡献他
    考虑对于每一个关键点, 对于他前面一个块跑一遍 01 背包, 对于他后面一个块跑一遍 01 背包
    查询的时候枚举关键点前面的部分用多少钱, 所有的情况取 (max) 即可
    复杂度是 (O(nt))
    不过这个方法挺仙的啊

    Code2

    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <vector>
    const int N = 2e4 + 5; 
    const int lim = 2e4; 
    #define pii pair<int, int>
    #define mp(i, j) make_pair(i, j)
    using namespace std;
    
    int n, p, r[N], l[N], cnt, m, ans; 
    vector<pii > vec[N];
    struct node { int f[4005]; } a[10005]; 
    
    template < typename T >
    inline T read()
    {
    	T x = 0, w = 1; char c = getchar();
    	while(c < '0' || c > '9') { if(c == '-') w = -1; c = getchar(); }
    	while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    	return x * w; 
    }
    
    void calc(int k, int c, int v)
    {
    	for(int i = 4000; i >= c; i--)
    		a[k].f[i] = max(a[k].f[i], a[k].f[i - c] + v); 
    }
    
    int main()
    {
    	n = read <int> (), p = read <int> ();
    	for(int c, h, t, i = 1; i <= n; i++)
    	{
    		c = read <int> (), h = read <int> (), t = read <int> (); 
    		vec[t].push_back(mp(c, h)); 
    	}
    	for(int sz, i = 1; i <= lim; i += p)
    	{
    		for(int j = 0; j < p && i + j <= lim; j++)
    		{
    			if(j) r[i + j] = r[i + j - 1]; 
    			if(vec[i + j].size())
    			{
    				sz = vec[i + j].size(), a[++cnt] = a[r[i + j]], r[i + j] = cnt; 
    				for(int k = 0; k < sz; k++)
    					calc(cnt, vec[i + j][k].first, vec[i + j][k].second); 
    			}
    		}
    		for(int j = 1; j < p && i - j >= 1; j++)
    		{
    			if(j > 1) l[i - j] = l[i - j + 1]; 
    			if(vec[i - j].size())
    			{
    				sz = vec[i - j].size(), a[++cnt] = a[l[i - j]], l[i - j] = cnt; 
    				for(int k = 0; k < sz; k++)
    					calc(cnt, vec[i - j][k].first, vec[i - j][k].second); 
    			}
    		}
    	}
    	m = read <int> (); 
    	int pos, k; 
    	while(m--)
    	{
    		pos = read <int> (), k = read <int> (), ans = 0;
    		for(int i = 0; i <= k; i++)
    			ans = max(ans, a[l[max(pos - p + 1, 0)]].f[i] + a[r[pos]].f[k - i]); 
    		printf("%d
    ", ans); 
    	}
    	return 0; 
    }
    
  • 相关阅读:
    多任务顺序执行解决方案
    数码摄影学习总结
    ASP.NET Core与RESTful API 开发实战(二)
    通过三点求圆心程序(二维和三维两种方式),代码为ABB机器人程序,其他语言也适用
    ABB机器人选项611-1 Path Recovery使用记录
    C#刷新chart控件方法及task的启停功能记录
    ABB机器人输送链跟踪问题记录
    有关C#跨线程操作控件的委托方法
    c#get、set属性及类的继承
    正则表达式学习记录
  • 原文地址:https://www.cnblogs.com/ztlztl/p/12796639.html
Copyright © 2011-2022 走看看