注意:本文还处于 EA 阶段,亟待修正和完善
最后一次更新时间:2021/11/10 20:55
注意:本文的代码可以AC,但解释可能有误,思路仅供参考
动态规划+线段树
参考:
https://blog.csdn.net/qq_39641976/article/details/111195335
https://blog.csdn.net/zearot/article/details/52280189
https://blog.csdn.net/zearot/article/details/48299459
《赛博朋克 2077》 购买链接(大雾) https://store.steampowered.com/app/1091500/_2077/
题目描述
今天,看了赛博朋克2077试完后的DD_BOND,决定买它买它买它!!看了看它的价格要298后,DD_BOND看了眼自己的余额,想想有没有便宜的方法可以购买。正巧,当DD_BOND打开steam后,他发现G胖正在搞促销,只需要集齐298298个不同的点数后,就可以兑换赛博朋克2077啦。
那么现在商店中一共有m
个礼包,第i
个礼包需要花费v[i]
的价格,礼包中包含l[i]~r[i]
的所有点数,DD_BOND比较笨蛋,想寻求你帮助他,告诉他最少需要花费多少价格可以集齐1
到298298
的所有点数,如果不可以,请告诉他-1
。
输入
一个整数m
(1<=m<=1e5
),表示有m
个礼包,接下来m
行,每行有l[i],r[i],v[i]
(1<=l[i]<=r[i]<=298298
,0<=v[i]<=1e9
)表示该礼包包含的点数和该礼包价格。
输出
若不能集齐1~298298
所有点数输出-1
,否则输出最小总价格。
样例输入
【样例1输入】
3
1 2077 1
2078 298298 2
2 298298 3
【样例2输入】
3
1 2077 1
2078 298297 3
2 21233 5
样例输出
【样例1输出】
3
【样例2输出】
-1
题目分析:
一、题意概括(来自参考链接1)
给出n
个区间,每段区间有三个值l,r,v
,v
表示花费v
的代价可以覆盖区间[l,r]
,求覆盖区间[1,298298]
的最小代价
二、用动态规划解题(关于DP部分,后文有不使用线段树的等价代码)
(1) 定义dp[]
的含义
dp[i]
定义为拥有前i
个点的最小花费
那么dp[298298]
为本题答案(由于使用线段树,实际上dp[298298+1]
才是答案,代码注释会提及)
(2) 找出dp[]
间的关系式(该部分亟待完善,可以先凑合看看2333)
首先对各个区间按右端点升序排序,右端点相同的情况按左端点升序排序
然后从前往后开始遍历每一个区间,对于当前区间l,r,v
来说,我们可以从dp[j]
(l-1<=j<=r)
转移到当前dp[k]
(l<=k<=r)
,可以得出状态转移方程
dp[r]=min(dp[r],dp[l-1 -> r]+v)
其中
dp[l-1 -> r] <=> dp[i],i∈[l-1,r]
(3) 找出初始条件
由于要找最小花费,规定dp[]
初始值为较大数INF
在这里 INF=0x3f3f3f3f3f3f3f3f
如果当前区间的l=1
,那么l-1=0
,为了让状态转移方程给dp[1]
赋上值,需要规定dp[0]=0
(这里的数组下标在代码中被右移一位,实际上dp[1]=0
)
三、用线段树模拟数组dp[]
详见代码,该部分日后完善
AC 代码如下
代码来源于以上三个参考链接,我根据自己的理解补充全文注释,如有谬误可及时指出
原代码应用了 快速读入
和 结构体内嵌比较函数
,以提升程序运行速度。为简化代码,我使用了更简单暴力的方法(cin
和sort()
的cmp()
参数)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define Inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int maxn = 3e5+5;
struct node{//结构体存每个区间
int l,r;
long long val;
}nod[maxn];
bool cmp(node x,node y){//对结构体sort()所需函数
if(x.r==y.r) return x.l<y.l;
else return x.r<y.r;//对各个区间按右端点升序排序,右端点相同按左端点升序排序
}
/*--------------------------*/
/*-----以下为线段树部分-----*/
/*--------------------------*/
ll n,a[maxn<<2];//数组存储线段树,开4倍空间
//x*2 等价于 x<<1
//x*4 等价于 x<<2
//x*2+1 等价于 x<<1|1
void pushup(int root){//更新节点信息,这里是求左右子节点的最小值
a[root]=min(a[root<<1],a[root<<1|1]);
//root*2是左下方节点,root*2+1是右下方节点
}
void build(int root,int l,int r){//建树
//root为当前节点在a[]的对应下标,即实际存储位置
//[l,r]表示当前节点区间
if(l==r){//到达叶子节点
a[root]=Inf;
//初始化节点值为Inf,用于比最小值,同时参与判断是否所有 Steam 点数都被覆盖
return ;
}
int mid=l+r>>1;//左右递归,等价于min=(l+r)/2
build(root<<1 ,l ,mid);
build(root<<1|1,mid+1,r );
pushup(root);//递归完,更新本节点信息
}
void update(int root,int l,int r,int pos,ll val){//点修改
//root,l,r含义同上
//pos为线段树所表达的数组dp的实际下标 pos∈[1,298298+1]
//value是想要修改成的值
if(l==r){//到达叶子节点
a[root]=min(a[root],val);//修改为最小值
return ;
}
int mid=l+r>>1;
//判断需要向左走还是向右走,从上往下一路走到叶子节点
if(pos<=mid) update(root<<1,l,mid,pos,val);
else update(root<<1|1,mid+1,r,pos,val);
//递归(走)完更新本节点信息
pushup(root);
}
ll query(int root,int l,int r,int ql,int qr){//区间查询
if(l>qr || r<ql) return Inf;
//查询范围超出[ql,qr],且没有交叉部分.同时参与判断是否所有 Steam 点数都被覆盖
else if(l>=ql && r<=qr) return a[root];//在区间内直接返回值
else{//有交叉部分,递归到交叉部分,返回交叉部分的值
int mid=l+r>>1;
return min(query(root<<1,l,mid,ql,qr),query(root<<1|1,mid+1,r,ql,qr));
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>nod[i].l>>nod[i].r>>nod[i].val;
nod[i].l++;nod[i].r++;
//由于线段树习惯从1开始建立,而nod[i].l-1在l=1时等于0,所以把所有区间向右移1位
}
sort(nod,nod+n,cmp);
/*以下为线段树部分*/
//线段树模拟数组dp[1 -> 298298+1]
build(1,1,maxn);//建树
update(1,1,maxn,1,0);//线段树所模拟的dp[1]赋初值为0
/*---下面这个for循环,后文有可参考的等价代码---*/
for(int i=0;i<n;i++){
ll minn=query(1,1,maxn,nod[i].l-1,nod[i].r);//查询dp[l-1 -> r]最小值
minn+=nod[i].val;//最小值加上新值
update(1,1,maxn,nod[i].r,minn);
}
/*------------------------------------------*/
ll ans=query(1,1,maxn,298298+1,298298+1);//查询线段树所模拟的数组dp[298298+1]
if(ans!=Inf) cout<<ans;
else cout<<-1;
return 0;
}
可供参考的等价代码
时间超限 AC40%
这段代码没有用线段树模拟数组dp[]
,直接暴力在dp[]
数组进行区间查询和修改。理论上功能是相同的,可帮助理解DP
部分
注意:没有充分的测试数据表明两段代码是等价的
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define Inf 0x3f3f3f3f3f3f3f3f
using namespace std;
const int maxn = 3e5+5;
struct node{
int l,r;
long long val;
}nod[maxn];
bool cmp(node x,node y){
if(x.r==y.r) return x.l<y.l;
else return x.r<y.r;
}
ll n,dp[maxn];
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>nod[i].l>>nod[i].r>>nod[i].val;
sort(nod,nod+n,cmp);
for(int i=1;i<=maxn;i++)
dp[i]=Inf;
for(int i=0;i<n;i++){
ll minn=Inf;
for(int j=nod[i].l-1;j<=nod[i].r;j++)
minn=min(minn,dp[j]);
minn+=nod[i].val;
for(int j=nod[i].l;j<=nod[i].r;j++)
dp[j]=min(minn,dp[j]);
}
ll ans=dp[298298];
if(ans<Inf) cout<<ans;
else cout<<-1<<endl;
return 0;
}