没有做过 Shopping Plans 的人生是不完整的。
没有做过 \(\text{Shopping Plans}\) 的人生是不完整的。
本质是关于一类求前 k 优解的问题。
Shopping Plans on Arrays
有 \(N\) 个商品,每个商品有个种类 \(a_i\),有个价格 \(c_i\)。
对于第 \(j\) 个种类,必须购买个数位于 \([x_j,y_j]\) 的商品,即最少购买 \(x_j\) 个,最多购买 \(y_j\) 个该种类的商品。
您需要求出前 \(K\) 种便宜的方案所需的价钱,如果没有这样的方案,请输出 -1。
特别的,如果有相同钱数,但是具体方案不相同的,算作两种方案。
思路
对于前 \(K\) 小方案问题。
我们可以考虑当前方案对下一个方案的转移。
重点在于转移的最优化与不重不漏。
只有一种种类
假设没有 \(l,r\) 的限制怎么做。
我们不妨把所有价格排序。
发现一种状态转移到另一种状态,无异与将其中已选择的一个物品不选,选择他后面的一个物品。
这样可以得到一个更劣的状态。
考虑使用 \((now, nxt, val)\) 来表示当前状态。
\(now\) 表示我们当前准备不选的物品。
\(nxt\) 表示 \(now\) 后面第一个选择的物品。
那么有转移:
当然,这样的转移没有包括所有的物品。
那么我们不妨在加入一维。
考虑使用 \((pre, now, nxt, val)\) 来表示当前状态。
\(pre\) 表示 \(now\) 前面有几个元素,由于 \(pre\) 个元素都没有位移过,所以它必然是一段 \(1\sim pre\) 的前缀。
那么有转移:
发现第二种转移与当前的方案是同一种,所以我们可以钦定每一次转移必须位移一次。
那么有:
初始状态为:
至于 \(l,r\) 的限制也变得比较简单。
有多种种类
可以发现,每一种种类之前是比较独立的。
所以我们同样可以用之前的方法求出单种种类的第 \(k\) 小。
设 \(kth_j(i)\) 为第 \(j\) 种颜色中,第 \(i\) 小的方案。
使用同样的状态维护外层的贪心。
\((now,k,val)\) 表示当前在第 \(now\) 个颜色,使用的是第 \(k\) 小方案。
那么有转移:
这样的转移还存在一点瑕疵。
我们有可能会跳过一些颜色。
那么对于 \(k=2,now\not = 1\),我们有转移:
这样,我们把所有颜色按 \(kth(1)-kth(2)\) 从大到小排序,就可以正常转移了。
初始状态 \((1,1,\sum kth(1))\)。
时间复杂度:\(O(k\log k)\)。
Shopping Plans on Tree
给定一棵 \(n\) 个节点的带权无根树。
你需要求树上权值前 \(k\) 小的连通块。
思路
考虑点分治。
可以将问题转化为树上带根权值前 \(k\) 小连通块。
可以发现在树上的东西的选择具有一定的依赖性。
具体而言即儿子的选择依赖于父亲的选择。
那么不妨提出每个点的 \(\text{dfn}\) 序。
若是选,我们就连边:
若是不选,我们连边:
这样,问题转化为 \(1 \to n + 1\) 的 \(k\) 短路问题。
时间复杂度:\(O(n\log n^2 + k\log k)\)。
听上去简单很多,但代码要写很多板子(点分治,\(k\) 短路,都比较难写)。
Shopping Plans on Permutation
给定一个长度为 \(n\) 的排列 \(p\) 和数组 \(a\)。
定义一个排列 \(q\) 的代价为 \(\sum a_i \times q_i\),求代价前 \(k\) 小的排列。
思路
首先对 \(a_i\) 降序排列。
那么此时的最优解为 \(\sum a_i\times i\)。
然后再此基础上调整拓展。
很容易想到拓展方式就是交换相邻的升序对。
考虑让每个排列被唯一得到:于是从后往前考虑每个人,然后让它往后 \(\text{swap}\) 若干步。
可以记录 \((p,x)\) ,表示我们把现在轮到从前往后第 \(p\) 个人一直向前 \(\text{swap}\),搞到了第 \(x\) 个位置,这里有 \(x>p\)。
初始状态为 \((i,i+1)\)。
第一种转移,也就是往前 \(\text{swap}\) 一个位置。
第二种转移,也就是换成前一个位置 \(\text{swap}\)。
注意到这里计算贡献需要知道整个序列的实时位置,可以使用可持久化数组(线段树)来保存,所以实际是个三元组 \((p,x,rt)\)。
但是我们还需要假如一个反悔机制,也就是我们不一定在前一个位置必定转移。
记录四元组:
表示在前 \(pre\) 个位置不动时,我们把现在轮到从前往后第 \(p\) 个人一直向前 \(\text{swap}\),搞到了第 \(x\) 个位置,这里有 \(x>p\)。
并且设 \(f_x=a_{x}-a_{x+1}\),也就是交换一次的代价。
那么有转移:
其中 \(u\) 是小于 \(pre\) 的 \(f_u\) 最小值的位置。
可以发现在转移中 \(u < pre\),设置这一步的目的是什么呢。
这样可以方便的表示出在 \(1\sim pre\) 这一些数中,交换的只有 \(u\) 与 \(u+1\) 两个数。
那么就给反悔提供了条件。
有转移:
其中 \(v\) 是第一个反悔后答案变大的点。
也就是在前缀 \(1\sim p-1\),\((f_u,u)\) 的后继。
这里可以用主席树上二分解决。
这样我们整个问题就基本解决了。
时间复杂度: \(O((n+k)\log n+k\log k)\)。
代码,这是碰都碰不了的话题
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define op(x) ((x & 1) ? x + 1 : x - 1)
#define odd(x) (x & 1)
#define even(x) (!odd(x))
#define lc(x) (x << 1)
#define rc(x) (lc(x) | 1)
#define lowbit(x) (x & -x)
#define mp(x, y) make_pair(x, y)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
using namespace std;
const int MAXN = 1e5 + 10, MAXM = 5e7 + 10;
ll n, k, a[MAXN], rk[MAXN], arr[MAXN];
namespace DS { // 维护前缀的主席树
const int MAXM = 2e6 + 10;
int rt[MAXN];
struct Node {
int lc, rc;
} t[MAXM];
int tot;
int mdf(int x, int l, int r, int pos) {
int p = ++tot;
t[p] = t[x];
if (l == r) return p;
int mid = (l + r) >> 1;
(pos <= mid) ? (t[p].lc = mdf(t[p].lc, l, mid, pos))
: (t[p].rc = mdf(t[p].rc, mid + 1, r, pos));
return p;
}
int qry(int x, int l, int r, int pos) { // 二分处下一个为1的位置
if (r <= pos || !x) return 0;
if (l == r) return r;
int mid = (l + r) >> 1;
int ret = qry(t[x].lc, l, mid, pos);
if (ret)
return ret;
else
return qry(t[x].rc, mid + 1, r, pos);
}
void build() {
rep(i, 1, n - 1) { rt[i] = mdf(rt[i - 1], 1, n - 1, rk[i]); }
}
}; // namespace DS
struct Node { // 用来维护序列的主席树
int lc, rc, val;
} t[MAXM];
int tot;
int build(int l, int r) {
int x = ++tot;
if (l == r) {
t[x].val = l;
return x;
}
int mid = (l + r) >> 1;
t[x].lc = build(l, mid);
t[x].rc = build(mid + 1, r);
return x;
}
int mdf(int x, int l, int r, int pos, int val, int md) {
int p = (!md) ? (++tot) : (x);
t[p] = t[x];
if (!p) p = ++tot;
if (l == r) {
t[p].val = val;
return p;
}
int mid = (l + r) >> 1;
(pos <= mid) ? (t[p].lc = mdf(t[p].lc, l, mid, pos, val, md))
: (t[p].rc = mdf(t[p].rc, mid + 1, r, pos, val, md));
return p;
}
int qry(int x, int l, int r, int pos) {
if (l == r) return (t[x].val);
int mid = (l + r) >> 1;
return (pos <= mid) ? (qry(t[x].lc, l, mid, pos))
: (qry(t[x].rc, mid + 1, r, pos));
}
int mdf(int rt, int p, int x, int y) {
int ret = mdf(rt, 1, n, p, x, 0);
ret = mdf(ret, 1, n, p + 1, y, 0);
return ret;
}
struct State {
ll sum;
bool operator<(const State& n2) const { return sum > n2.sum; }
int now; // 当前在移动的值为now
int pos; // 移动到了位置pos(pos>now)
int cur;
// 如果是正常转移,则cur = now-1,
// 否则cur是可选取的前缀范围,意味着cur>=pos
int rt; // p序列
};
priority_queue<State> pq;
map<string, int> ok;
void ext(State ts) {
ll sum = ts.sum;
int now = ts.now, pos = ts.pos, cur = ts.cur, rt = ts.rt;
// 1. 继续向后交换一步
if (pos < n) {
int nxt = qry(rt, 1, n, pos + 1);
int nrt = mdf(rt, pos, nxt, now);
ll nsum = sum + (nxt - now) * (a[pos] - a[pos + 1]);
pq.push((State){nsum, now, pos + 1, now - 1, nrt});
}
// 2. 上一个人交换一步
if (now > 1) {
int hd = qry(rt, 1, n, now);
int nrt = mdf(rt, now - 1, hd, now - 1);
ll nsum = sum + (hd - now + 1) * (a[now - 1] - a[now]);
pq.push((State){nsum, now - 1, now, now - 2, nrt});
}
// 3. 跳过上一个人,进入特殊状态
if (now > 2) {
int num = DS::qry(DS::rt[now - 2], 1, n - 1, 0);
assert(num);
num = arr[num];
assert(num < now - 1);
int nrt = mdf(rt, num, num + 1, num);
ll nsum = sum + (a[num] - a[num + 1]);
pq.push((State){nsum, num, num + 1, now - 1, nrt});
}
if (now <= cur) { // 带反悔选项
assert(pos == now + 1 && cur >= pos);
// 3. 反悔
int num = DS::qry(DS::rt[cur - 1], 1, n - 1, rk[now]);
if (num) {
num = arr[num];
assert(num < cur);
int nrt = mdf(rt, now, now, now + 1);
nrt = mdf(nrt, num, num + 1, num);
ll nsum = sum - (a[now] - a[now + 1]) + (a[num] - a[num + 1]);
assert(nsum >= sum);
pq.push((State){nsum, num, num + 1, cur, nrt});
}
}
}
int main() {
ios::sync_with_stdio(false);
cin >> n >> k;
k--;
rep(i, 1, n) cin >> a[i];
sort(a + 1, a + 1 + n, greater<ll>());
ll sum = 0;
rep(i, 1, n) sum += a[i] * i;
cout << sum << endl;
if (!k) exit(0);
rep(i, 1, n - 1) arr[i] = i;
sort(arr + 1, arr + n,
[&](int x, int y) { return a[x] - a[x + 1] < a[y] - a[y + 1]; });
rep(i, 1, n - 1) rk[arr[i]] = i;
int rt = build(1, n);
DS::build();
rep(i, 1, n - 1) { // 第一步交换i和i+1,再往后的不变
ll nows = sum + a[i] - a[i + 1];
int nowrt = mdf(rt, i, i + 1, i);
pq.push((State){nows, i, i + 1, i - 1, nowrt});
}
while (k--) {
assert(pq.size());
State now = pq.top();
pq.pop();
cout << now.sum << "\n";
ext(now);
}
return 0;
}

浙公网安备 33010602011771号