码迷,mamicode.com
首页 > 其他好文 > 详细

[题解] [CF500F] New Year Shopping

时间:2020-04-28 20:27:52      阅读:63      评论:0      收藏:0      [点我收藏+]

标签:print   span   线段树   ace   return   cal   ring   ping   alc   

题面

题解

提供两种方法

线段树分治

将一个物品可以购买的时间区间打到线段树上
考虑对于每一个点如何算贡献
从线段树的根开始做 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\n", 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\n", ans); 
	}
	return 0; 
}

[题解] [CF500F] New Year Shopping

标签:print   span   线段树   ace   return   cal   ring   ping   alc   

原文地址:https://www.cnblogs.com/ztlztl/p/12796639.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!