【YBT2023寒假Day9 B】买棉花糖(DP)(分治)
买棉花糖
题目链接:YBT2023寒假Day9 B
题目大意
有 n 个商店,每个商店有 ci 个物品,原价是 ai,你在一个商店买的物品越多,下一个买的就越少,每次减少 di 块钱。
然后有 q 次询问,每次问你买 mi 个物品的最小费用。
思路
首先由一个结论,就是确定总物品数的情况下,最优情况中最多一家店选的物品数量不是 0 也不是全部。
感性的看也确实是这样,因为如果买这个越买越赚就肯定是买更多,不过还是不是那么显然,我们考虑证明:
那如果上面的条件成立,那有两家点 \(i,j\),分别选了 \(x,y\) 个。
假设 \(b_{i,j}\) 是在第 \(i\) 个商店买第 \(j\) 次的价格,那 \(b_{i,j}=a_i-(j-1)d_i\)
那我们首先有 \(b_{i,x}> b_{i,x+1},b_{j,y}>b_{j,y+1}\)。
那如果满足最优,那就是无论 \(x+1,y-1\) 和 \(x-1,y+1\) 都不优。
那就是 \(b_{i,x}\leqslant b_{j,y+1},b_{j,y}\leqslant b_{i,x+1}\)
然后联立这个式子和上面的第一个。
\(b_{j,y}\leqslant b_{i,x+1}<b_{i,x}\leqslant b_{j,y+1}\)
那就是 \(b_{j,y}<b_{j,y+1}\),这与上面的第二个 \(b_{j,y}>b_{y,j+1}\) 矛盾。
所以成立。
然后考虑通过看特别的那个位置在哪里,考虑分治。(\(f_{l,r,x}\) 是第 \(l\sim r\) 个商店里面选 \(x\) 个的代价)
那如果是 \([l,mid]\),那你可以先左边递归下去得到 \(g_m=f_{l,mid,m}\),然后再枚举右边 \([mid+1,r]\),用一个背包的操作(不加或者全加),算入到 \(g\) 里面。
右边也同理,那背包一次是 \(O(nm)\) 的,总复杂度就是 \(O(nm\log n)\)
代码
#include<cstdio>
#include<iostream>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 550;
const int M = 2e4 + 100;
int n, q;
ll a[N], d[N], c[N], g[N << 2][M];
void slove(int now, int l, int r) {
if (l == r) {
for (int i = 0; i <= c[l]; i++) g[now][i] = a[l] * i - d[l] * (i - 1) * i / 2;
for (int i = c[l] + 1; i <= 20000; i++) g[now][i] = INF;
return ;
}
int mid = (l + r) >> 1;
slove(now << 1, l, mid);
for (int i = mid + 1; i <= r; i++) {
for (int j = 20000 - c[i]; j >= 0; j--)
g[now << 1][j + c[i]] = min(g[now << 1][j + c[i]], g[now << 1][j] + a[i] * c[i] - d[i] * (c[i] - 1) * c[i] / 2);
}
slove(now << 1 | 1, mid + 1, r);
for (int i = l; i <= mid; i++) {
for (int j = 20000 - c[i]; j >= 0; j--)
g[now << 1 | 1][j + c[i]] = min(g[now << 1 | 1][j + c[i]], g[now << 1 | 1][j] + a[i] * c[i] - d[i] * (c[i] - 1) * c[i] / 2);
}
for (int i = 0; i <= 20000; i++) g[now][i] = min(g[now << 1][i], g[now << 1 | 1][i]);
}
int main() {
freopen("marshmallow.in", "r", stdin);
freopen("marshmallow.out", "w", stdout);
// freopen("easy2.in", "r", stdin);
// freopen("write.txt", "w", stdout);
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%lld %lld %lld", &a[i], &d[i], &c[i]); c[i] = min(c[i], 20000ll);
}
slove(1, 1, n);
for (int i = 1; i <= q; i++) {
int x; scanf("%d", &x); printf("%lld\n", g[1][x]);
}
return 0;
}