K-th 问题的一般思路
是在同一个情景下,求出前
其可以等效转化为:
将每一种方案视作一个状态,并通过状态之间的大小关系连边(严格),我们求出其拓扑序的前
个节点。
笔者认为,所有的优化方案本质上都是在尽可能少的边数下保留这个拓扑结构,亦或者是利用隐式建图等技巧(因为事实上绝大部分情况状态相当多且复杂)
看几个情景:
情景一
给定序列
,你需要从中选出一个数,求选出数权值前 小的方案。
排序取前
我们分别根据其向两个方向的 Expand 来设立情景二与情景三
情景二
从扩展维度入手。
给定两个序列
,定义方案 的权值是 ,求前 小方案。
先将
朴素方法
使用小根堆,加入 continue
,将
前
这种方法实现简单,思维量小,但是有其弊端:判重。
这启发我们 钦定唯一前驱
优化
一般会自然的考虑指定
无
这样仍然保证了每个状态的后继不超过
但是要保证**不会有非最小状态没有前驱
更高维的情形
给定
个序列 ,定义方案 的权值是 ,求其前 小方案。
可能我们会自然地沿用上一种情形的构造方法,从后往前找到第一个非零的
但是这样存在一个问题,对于一个后面有若干个零的状态而言,其后继状态可能会到达
是容易爆炸的
考虑规避掉这种情况,也就是告诉我们将
不妨钦定若
这样可以做一个正向类似于轮廓线DP的事情,在逆向角度看就是将一个
用形式语言描述就是,不妨设
这样我们就保证了任意状态的前驱如果存在那么唯一,且后继只会出现如下情况:
设最后一个非零位为
增加- 将
改为 ,将 改为 ,将 改为零
所以每个状态的后继不超过
不过我们仍然需要研究一下状态的表达,毕竟你不可能真的将状态用
这是容易的,我们发现我们只需要记录下
情景三
从扩展选择方法入手。
给定序列
,定义一个选择方案 的权值是 ,求出大小为 的前 个选择方案。
同样先排序。
我们从二进制的角度入手,同样考虑为每个状态指定唯一前驱。
不妨考虑由
让我们更加形式化一点,状态
找到第一个
- 继续移动
,这要求 - 换个继续动,也就是令
。
同样的考虑简化当前决策的表达,发现我们只需要维护
Expand
当
很简单,我们插入
[CCO2020] Shopping Plans
将情景三所用算法维护每一类物品,然后用情景二所用方法维护答案即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 1050500
const int inf=0x3f3f3f3f3f3f3fll;
int id[N];
struct stu{
int i,pi,pi_1,s;
stu(){
i=pi=pi_1=s=0;
}
stu(int _i,int _pi,int _pi_1,int _s){
i=_i,pi=_pi,pi_1=_pi_1,s=_s;
}
bool operator<(const stu b)const {
return s>b.s;
}
};
int n,m,K;
struct node{
//单个
int l,r,n;
vector<int>c,ans;
priority_queue<stu >q;
void init(){
n=c.size();
if(l>n){while(K--){cout<<"-1\n";}exit(0); }
r=min(r,n);
sort(c.begin(),c.end());
if(!l)ans.push_back(0);
for(int i=0,s=0;i<r;++i){
s+=c[i];
if(i+1>=l)q.emplace(i,i,n,s);
}
}
int get(int k){
while(!q.empty()&&ans.size()<k){
auto [i,pi,pi_1,s]=q.top();q.pop();
ans.push_back(s);
if(pi+1<pi_1)q.emplace(i,pi+1,pi_1,s+c[pi+1]-c[pi]);
if(i&&i<pi)q.emplace(i-1,i,pi,s+c[i]-c[i-1]);
}
if(ans.size()<k)return inf;
return ans[k-1];
}
}a[N];
void init(){
cin>>n>>m>>K;
for(int i=1,tp,w;i<=n;++i){
cin>>tp>>w;
a[tp].c.push_back(w);
}
for(int i=1;i<=m;++i)cin>>a[i].l>>a[i].r,id[i]=i;
for(int i=1;i<=m;++i)a[i].init();
sort(id+1,id+m+1,[&](int x,int y){return a[x].get(2)-a[x].get(1)<a[y].get(2)-a[y].get(1);});
}
void sol(){
priority_queue<stu>q;
int s=0;for(int i=1;i<=m;++i)s+=a[i].get(1);
cout<<s<<"\n";--K;
q.emplace(1,2,0,s+a[id[1]].get(2)-a[id[1]].get(1));
while(!q.empty()&&K){
auto [i,p,sbzxy,s]=q.top();q.pop();
if(s>2e14)break;
cout<<s<<"\n";
q.emplace(i,p+1,0,s+a[id[i]].get(p+1)-a[id[i]].get(p));
if(i!=m){
q.emplace(i+1,2,0,s+a[id[i+1]].get(2)-a[id[i+1]].get(1));
if(p==2)q.emplace(i+1,2,0,s+a[id[i]].get(1)-a[id[i]].get(2)+a[id[i+1]].get(2)-a[id[i+1]].get(1));
}--K;
}
while(K--)cout<<"-1\n";
}
signed main(){
init();sol();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!