[题解] [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\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;
}