【李超线段树-“直线”型模板】JSOI2008——Blue Mary开公司

前言

用老师给的模板前前后后改了四五天...后来改用别人的模板...

用别人的模板在洛谷上过了,结果Vjudge上过不了...

多亏某Tao大佬发现计算y值用的int,改成double才终于过了...

看着别人都是几天前一遍过...我...说多了都是泪...

题目

万事开头难,经营公司更是如此。开始的收益往往是很低的,不过随着时间的增长会慢慢变好。也就是说,对于一个金融顾问 i,他设计的经营方案中,每天的收益都比前一天高,并且均增长一个相同的量 Pi。

由于金融顾问的工作效率不高,**所以在特定的时间,Blue Mary 只能根据他已经得到的经营方案来估算某一时间的最大收益。**由于 Blue Mary 是很没有经济头脑的,所以他在估算每天的最佳获益时完全不会考虑之前的情况,而是直接从所有金融顾问的方案中选择一个在当天获益最大的方案的当天的获益值,例如:

有如下两个金融顾问分别对前四天的收益方案做了设计:

 第一天第二天第三天第四天Pi
顾问 1159134
顾问 2258113

在第一天,Blue Mary认为最大收益是 2(使用顾问 2 的方案),而在第三天和第四天,他认为最大收益分别是 9 和 13(使用顾问 1 的方案)。而他认为前四天的最大收益是:

2+5+9+13=29

现在你作为 Blue Mary 公司的副总经理,会不时收到金融顾问的设计方案,也需要随时回答 Blue Mary 对某天的“最大收益”的询问(这里的“最大收益”是按照 Blue Mary 的计算方法)。**一开始没有收到任何方案时,你可以认为每天的最大收益值是 0。**下面是一组收到方案和回答询问的例子:

询问 2
回答 0
收到方案:0 1 2 3 4 5 ……
询问 2
回答 1
收到方案:2 2.1 2.2 2.3 2.4 ……
询问 2
回答 2.1

输入格式

第一行 :一个整数 N ,表示方案和询问的总数。

接下来 N 行,每行开头一个单词QueryProject

若单词为Query,则后接一个整数 T,表示 Blue Mary 询问第 T 天的最大收益。

若单词为Project,则后接两个实数 S,P,表示该种设计方案第一天的收益 S,以及以后每天比上一天多出的收益 P。

输出格式

对于每一个Query,输出一个整数,表示询问的答案,并精确到整百元(以百元为单位,例如:该天最大收益为 210 或 290 时,均应该输出 2)。没有方案时回答询问要输出 0。

输入输出样例

输入 #1

10
Project 5.10200 0.65000
Project 2.76200 1.43000
Query 4
Query 2
Project 3.80200 1.17000
Query 2
Query 3
Query 1
Project 4.58200 0.91000
Project 5.36200 0.39000

输出 #1

0
0
0
0
0

说明/提示

数据范围:

1≤N≤10^5,1≤T≤50000,0<P<100,∣S∣≤10^5

提示:

本题读写数据量可能相当巨大,请选手注意选择高效的文件读写方式。

题目大意

给定一些直线,求 x 点上 f(x) 的最大值

分析

参考博客&特别鸣谢:Blue Mary开公司

【关于“李超树”】

李超树是用来维护一些直线的,通常来说,我们需要建立一棵线段树,线段树上的节点代表一个区间

Ps.这道题的数据是“直线”型,也就是无限长的,不用考虑两线之间的覆盖关系,个人觉得很好想,

但“线段”型数据的处理方法有所不同,会更复杂一点

对于每一个线段树上的节点,我们假设其区间为 [L,R] 且中点为 Mid ,则该节点上保存的直线为其子树保存的直线中 f(Mid)最大的一条直线

对于插入某一条直线,考虑递归

(“当前直线”为红色,“原直线”为蓝色,“中线”为Mid的位置)

  • 如果当前直线在 Mid处比原直线在 Mid处更优,则需要替换原直线
    • 若当前直线斜率较大,则将原直线递归至左子树,因为原直线只可能对左子树造成贡献
    • 若原直线斜率较大,则递归右子树
  • 否则将当前直线递归
    • 若当前直线斜率较大,则递归右子树
    • 若原直线斜率较大,则递归左子树

这样每个节点上保存的直线都是在其子树中 f(Mid)最大的一个直线

询问只需要从根到某个单点取 f(x)的最大值

(良心画图有木有qwq)

代码

//上个版本改哭了...换了个版本... 
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=2e5+5,MAXX=5e4;
struct line{double b,k;};
struct node{line p;bool flag;}tree[MAXN<<2];
char s[100];
int n;
double f(line p,int pos)//计算y值 
{
	return p.k*pos+p.b;
}
void update(int i,int l,int r,line p)
{
	if(!tree[i].flag)
	{
		tree[i].flag=true;
		tree[i].p=p;
		return ;
	}
	if(l==r)
	{
		if(f(tree[i].p,l)<f(p,l))
			tree[i].p=p;
		return ;
	}
	int mid=(l+r)>>1;
	if(p.k>tree[i].p.k)
	{
		if(f(p,mid)>f(tree[i].p,mid))
		{
			update(i*2,l,mid,tree[i].p);
			tree[i].p=p;
		}
		else
			update(i*2+1,mid+1,r,p);		
	}
	else
	{
		if(f(p,mid)>f(tree[i].p,mid))
		{
			update(i*2+1,mid+1,r,tree[i].p);
			tree[i].p=p;
		}
		else
			update(i*2,l,mid,p);
	}
}
int query(int i,int l,int r,int pos)
{
	if(!tree[i].flag)
		return 0;
	if(l==r)
		return f(tree[i].p,pos);
	int mid=(l+r)>>1,ans=f(tree[i].p,pos);
	if(pos<=mid)
		ans=max(ans,query(i*2,l,mid,pos));
	else
		ans=max(ans,query(i*2+1,mid+1,r,pos));
	return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		if(s[0]=='P')
		{
			line tmp;
			scanf("%lf%lf",&tmp.b,&tmp.k);
			tmp.b-=tmp.k;
			update(1,1,MAXX,tmp);
		}
		else
		{
			int tmp;
			scanf("%d",&tmp);
			printf("%d\n",(int)query(1,1,MAXX,tmp)/100);
		}
	}
	return 0;
}

WA的玄学“线段型”模板代码

//超哥树qwq...
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=5e5+5,MAXX=5e4+5;
char task[100];
double k,b;
int n,date,cnt;
struct node//完全二叉树储存线段树 
{
	double k,b;//区间最优势线段的k,b
	int l,r,flag;//flag为该区间已经记录最优势线段标记
	node(){}//构造函数 
	node(int _a,int _b,double _c,double _d)
	{
		l=_a,r=_b,k=_c,b=_d;
	} 
	double get_y(const int x) const//返回线段在x处的y值
	{
		return k*x+b;
	}
	int get_x(const node rhs) const//求两线段交点的横坐标 
	{
		return floor((b-rhs.b)/(rhs.k-k));
	}
}tree[MAXN<<2];//空间不爆炸的话开4倍保险 
void build(int i,int l,int r)//建树 
{
	tree[i].k=tree[i].b=0;
	tree[i].l=l,tree[i].r=r;
	if(l==r)
		return ;
	int mid=(l+r)>>1;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
}
void add(int i,int l,int r,node p)
{
	if(p.l<=l&&r<=p.r)//被插入线段完全覆盖了当前区间
	{
		//1.当前无优势线段
		if(!tree[i].flag)
		{
			tree[i]=p;
			tree[i].flag=true;
		}
		//2.如果插入的线段优于区间最优势线段,更新
		else if(p.get_y(l)>tree[i].get_y(l)&&p.get_y(r)>tree[i].get_y(r))
			tree[i]=p;
		//3.如果插入的线段只有一端优于最优势线段,则检查
		else if(p.get_y(l)>tree[i].get_y(l)||p.get_y(r)>tree[i].get_y(r))
		{
			int mid=(l+r)>>1;
			if(p.get_y(mid)>tree[i].get_y(mid))//看区间中点哪条线段更优 
			{
				node tmp=p;p=tree[i];tree[i]=tmp;
			}
			if(p.get_x(tree[i])<mid)
				add(i*2,l,mid,p);
			else
				add(i*2,mid+1,r,p);
		}
	}
	//未完全覆盖当前区间,则检查左右区间
	else
	{
		int mid=(l+r)>>1;
		if(p.l<=mid)
			add(i*2,l,mid,p);
		if(p.r>mid)
			add(i*2+1,mid+1,r,p);
	}
}
double query(int i,int l,int r,int x)//查询区间最优势线段在x处取值(常规线段树操作) 
{
	if(l==r)
		return tree[i].get_y(x);
	int mid=(l+r)>>1;
	double ans=tree[i].get_y(x);
	if(x<=mid)
		return max(ans,query(i*2,l,mid,x));
	else
		return max(ans,query(i*2+1,mid+1,r,x));
}
int main()
{
	build(1,1,MAXX);//要致富,先建树 
	int n;
	scanf("%d",&n);
	while(n--)
	{
		scanf("%s",task);
		if(task[0]=='P')
		{
			scanf("%lf%lf",&b,&k);
			tree[++cnt]=node(1,MAXX,k,b-k);
			add(1,1,MAXX,tree[cnt]);
		}
		else
		{
			scanf("%d",&date);
			printf("%d\n",(int)query(1,1,MAXX,date)/100);
		}
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值