【笔记】kth - 浅谈前 k 优解问题
【笔记】kth - 浅谈前 k 优解问题
第一次见到这一类的 trick 是在 SDOI2013 - 淘金,现在才知道这个 trick 还有一堆扩展。
Part 0.
这类问题的一个通用思路:
对于目前考虑到的一个状态
首先将最优的状态
- 取出堆顶状态
,计算 的答案。 - 将
中的状态放入堆中。
可以注意到,如果
Part 1. Multiset
- 给定一个可重集
,求大小为 的第 小子集和。
首先排序,最优状态一定是选排序后的前
考虑每次转移,即将状态中的一个数替换为另一个数,设一个状态
初始把
- 给定一个可重集
,求大小在 之间的第 小子集和。
初始把
- 给定一个可重集
,求第 小子集和,可以为空。
依然可以延续 2. 的做法。
#include <bits/stdc++.h> using namespace std; const int N = 5e5 + 10; int n, k; typedef long long ll; ll w[N], s; struct node{ ll val; int x, y, z; bool operator < (const node &y) const { return val > y.val; } node(ll v, int X, int Y, int Z){ val = v; x = X; y = Y; z = Z; } }; int main(){ scanf("%d%d", &n, &k); for(int i = 1; i <= n; ++ i){ scanf("%lld", &w[i]); } sort(w + 1, w + n + 1); priority_queue<node> q; ll sum = 0; for(int i = 0; i <= n; ++ i){//[l,r] 在这里改就行 sum += w[i]; q.push(node(sum, i, n+1, n+1)); } while(k--){ auto p = q.top(); q.pop(); printf("%lld\n", p.val + s); if(p.y + 1 < p.z){ q.push(node(p.val-w[p.y]+w[p.y+1], p.x, p.y+1, p.z)); } if(p.x >= 1 && p.x + 1 < p.y){ q.push(node(p.val-w[p.x]+w[p.x+1], p.x-1, p.x+1, p.y)); } } return 0; }
但是还有一种更简单的做法:状态中只有
Part 2. Arrays
个数组,每个数组中选 个,求第 小和。、
将所有数组排序,初始状态显然应当为全部取第一个。
状态设计为
具体地,我们强制每次考虑下一个数组时转移为
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 10; int n, nn, k; ll sum, ans; struct arr{ int m, p[13]; } a[N]; bool cmp(arr x, arr y){ return x.p[2] - x.p[1] < y.p[2] - y.p[1]; } struct state{ ll val; int x, y; bool operator < (const state &y) const { return val > y.val; } state(ll v, int X, int Y){ val = v; x = X; y = Y; } }; int main(){ scanf("%d%d", &n, &k); for(int i = 1; i <= n; ++ i){ int mm = 0; scanf("%d", &mm); if(mm == 1){ int p; scanf("%d", &p); sum += p; } else { ++ nn; a[nn].m = mm; for(int j = 1; j <= a[nn].m; ++ j){ scanf("%d", &a[nn].p[j]); } sort(a[nn].p + 1, a[nn].p + a[nn].m + 1); sum += a[nn].p[1]; } } sort(a + 1, a + nn + 1, cmp); priority_queue<state> q; q.push(state(sum, 0, 0)); while(k--){ auto p = q.top(); q.pop(); ans += p.val; if(p.x && p.y + 1 <= a[p.x].m){ q.push(state(p.val + a[p.x].p[p.y+1] - a[p.x].p[p.y], p.x, p.y+1)); } if(p.x < nn){ q.push(state(p.val + a[p.x+1].p[2] - a[p.x+1].p[1], p.x+1, 2)); } if(p.y == 2 && p.x < nn){ ll tmp = a[p.x+1].p[2] - a[p.x+1].p[1] - (a[p.x].p[2] - a[p.x].p[1]); q.push(state(p.val + tmp, p.x+1, 2)); } } printf("%lld\n", ans); return 0; }
Part 3. Multisets
给定若干个可重集,第
个可重集可以选 个元素,求前 小和。
把每个可重集之间按 Arrays 的方案处理,单个可重集内按 Multiset 处理,相当于上面两个做法套起来。具体细节见代码。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e5 + 10; int n, m, k, nn; struct mulst{ vector<int> g; int l, r, sz; vector<ll> anss; struct node{ ll val; int x, y, z; bool operator < (const node &y) const { return val > y.val; } node(ll v, int X, int Y, int Z){ val = v; x = X; y = Y; z = Z; } }; priority_queue<node> q; void init(){ sz = g.size(); sort(g.begin(), g.end()); anss.push_back(-1); ll sum = 0; for(int i = 0; i <= sz; ++ i){ if(l <= i && i <= r){ q.push(node(sum, i-1, sz, sz)); } if(i != sz){ sum += g[i]; } } } ll calc(int kk){ if(anss.size() >= kk + 1 && anss[kk] != -1){ return anss[kk]; } if(q.empty()){ anss.push_back(-1); return -1; } auto p = q.top(); q.pop(); anss.push_back(p.val); if(p.y + 1 < p.z){ q.push(node(p.val - g[p.y] + g[p.y+1], p.x, p.y+1, p.z)); } if(p.x >= 0 && p.x + 1 < p.y){ q.push(node(p.val - g[p.x] + g[p.x+1], p.x-1, p.x+1, p.y)); } return anss[kk]; } } s[N], t[N]; bool cmp(mulst x, mulst y){ return x.anss[2] - x.anss[1] < y.anss[2] - y.anss[1]; } struct state{ ll val; int x, y; bool operator < (const state &y) const { return val > y.val; } state(ll v, int X, int Y){ val = v; x = X; y = Y; } }; int main(){ bool flg = 1; ll sum = 0; scanf("%d%d%d", &n, &m, &k); for(int i = 1; i <= n; ++ i){ int a, c; scanf("%d%d", &a, &c); s[a].g.push_back(c); } for(int i = 1; i <= m; ++ i){ scanf("%d%d", &s[i].l, &s[i].r); s[i].init(); if(s[i].calc(1) == -1){ while(k--){ puts("-1"); } return 0; } else if(s[i].calc(2) != -1){ t[++nn] = s[i]; } sum += s[i].anss[1]; } sort(t + 1, t + nn + 1, cmp); for(int i = 1; i <= nn; ++ i){ t[i].calc(1); t[i].calc(2); } priority_queue<state> q; q.push(state(sum, 0, 0)); while(k--){ if(q.empty()){ puts("-1"); continue; } auto p = q.top(); printf("%lld\n", p.val); q.pop(); if(p.x && t[p.x].calc(p.y+1) != -1){ q.push(state(p.val + t[p.x].anss[p.y+1] - t[p.x].anss[p.y], p.x, p.y + 1)); } if(p.x < nn){ q.push(state(p.val + t[p.x+1].anss[2] - t[p.x+1].anss[1], p.x+1, 2)); } if(p.y == 2 && p.x < nn){ ll tmp = t[p.x+1].anss[2] - t[p.x+1].anss[1] - (t[p.x].anss[2] - t[p.x].anss[1]); q.push(state(p.val + tmp, p.x+1, 2)); } } return 0; }
Part 4. Tree
求树上第
小连通块点权和。
首先考虑含根的连通块。
对树进行一个 dfs 求出 dfs 序,那么建一张新图,
对于不含根的连通块,就点分治,转化为若干个含根连通块的情况,就像 Part 3. 一样套起来。
Part 5. Permutation
给定
求 ,其中 为排列。
不会。
Part 6. k-shortest path
求图上第
短路。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步