2020年浙江理工大学新生赛 E DD_BOND买赛博朋克2077

44 篇文章 4 订阅
8 篇文章 0 订阅

注意:本文还处于 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比较笨蛋,想寻求你帮助他,告诉他最少需要花费多少价格可以集齐1298298的所有点数,如果不可以,请告诉他-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,vv表示花费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 代码如下

代码来源于以上三个参考链接,我根据自己的理解补充全文注释,如有谬误可及时指出

原代码应用了 快速读入结构体内嵌比较函数,以提升程序运行速度。为简化代码,我使用了更简单暴力的方法(cinsort()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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值