一、知识点学习
https://www.cnblogs.com/JHSeng/p/10896570.html
二、题目笔记
1、 [HEOI2013]Segment
题目描述
要求在平面直角坐标系下维护两个操作:
- 在平面上加入一条线段。记第 i 条被插入的线段的标号为 i。
- 给定一个数 k,询问与直线 x=k 相交的线段中,交点纵坐标最大的线段的编号。
输入格式
输出格式
对于每次查询,输出一行一个整数,代表交点纵坐标最大的线段的编号。若不存在任何一条线段与查询直线有交,则输出 0;若有多条线段与查询直线的交点纵坐标都是最大的,则输出编号最小的线段,同时 lastans 也应更新为编号最小的一条线段。
输入输出样例
输入 #1
6
1 8 5 10 8
1 6 7 2 6
0 2
0 9
1 4 7 6 7
0 5
输出 #1
2
0
3
说明/提示
样例输入输出 1 解释
对于第一次操作,解密后为 1 8 5 10 8
。
对于第二次操作,解密后为 1 6 7 2 6
。
对于第三次操作,解密后为 0 2
,此时 lastans 被更新为 2。
对于第四次操作,解密后为 0 11
,此时 lastans 被更新为 0。
对于第五次操作,解密后为 1 4 7 6 7
。
对于第六次操作,解密后为 0 5
。
数据范围与约定
对于 30% 的数据,保证 n≤10^3。
对于 100% 的数据,保证 1≤n≤105,1≤*k*,*x*0,*x*1≤39989,1≤*y*0,*y*1≤109。
提示
不保证 x0!=x1。对于一条 x0′=x1′ 的线段,认为其与 x=x0′ 的交点为其两端点中纵坐标较大的端点。
线段树维护每个横坐标对应的最大纵坐标值,就是维护最靠上的线段
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
//Start
#define lng long long
#define db double
#define mk make_pair
#define pb push_back
#define fi first
#define se second
#define rz resize
using namespace std;
const int inf=0x3f3f3f3f;
const lng INF=0x3f3f3f3f3f3f3f3f;
//Data
const int N=1e5,M=39989;
int n,lcnt;
typedef pair<db,db> line;
db f(line x,int X){return x.fi*X+x.se;}
db inter(line x,line y){return (y.se-x.se)/(x.fi-y.fi);}
line li[N+7];
int v[(N<<2)+7]; bitset<(N<<2)+7> ma;
void add(int x, int L, int R, int k = 1, int l = 1, int r = M){
int mid = (l + r) >> 1;
if(L <= l && r <= R){
if(!ma[k]){
v[k] = x;
ma[k] = 1;
return;
}
db ly1 = f(li[x], l), ry1 = f(li[x], r), ly = f(li[v[k]], l), ry = f(li[v[k]], r);
if(ly1 <= ly && ry1 <= ry){
return;
}
if(ly1 >= ly && ry1 >= ry){
v[k] = x;
return;
}
db in = inter(li[v[k]], li[x]);
if(ly1 >= ly){
if(in <= mid){
add(x, L, R, k << 1, l, mid);
}
else{
add(v[k], L, R, k << 1 | 1, mid + 1, r);
v[k] = x;
}
}
else{
if(in > mid){
add(x, L, R, k << 1 | 1, mid + 1, r);
}
else{
add(v[k], L, R, k << 1, l, mid);
v[k] = x;
}
}
return;
}
if(mid >= L){
add(x, L, R, k << 1, l, mid);
}
if(mid < R){
add(x, L, R, k << 1 | 1, mid + 1, r);
}
}
int locmp(int X, int x, int y){
return f(li[x], X) > f(li[y], X) ? x : y;
}
int get(int X, int k = 1, int l = 1, int r = M){
int res = 0;
if(ma[k]){
res = locmp(X, res, v[k]);
}
if(l == r){
return res;
}
int mid = (l + r) >> 1;
if(mid >= X){
res = locmp(X, res, get(X, k << 1, l, mid));
}
else{
res = locmp(X, res, get(X, k << 1 | 1, mid + 1, r));
}
return res;
}
int ans;
void h(int &x, int mod){
x = (x + ans - 1) % mod + 1;
}
int main()
{
ios::sync_with_stdio(false);
scanf("%d",&n);
for(int i = 1; i <= n; i++){
int op;
scanf("%d",&op);
if(op){
int xa, ya, xb, yb;
scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
h(xa, M), h(ya, 1e9), h(xb, M), h(yb, 1e9);
li[++lcnt] = xa == xb ? mk(.0, db(max(ya, yb))) : mk(db(yb - ya) / (xb - xa), - db(yb - ya) / (xb - xa) * xa + ya);
add(lcnt, min(xa, xb), max(xa, xb));
}
else{
int k;
scanf("%d",&k);
h(k, M);
printf("%d\n",ans=get(k));
}
}
return 0;
}
2、[JSOI2008]Blue Mary开公司
题目背景
Blue Mary 最近在筹备开一家自己的网络公司。由于他缺乏经济头脑,所以先后聘请了若干个金融顾问为他设计经营方案。
题目描述
万事开头难,经营公司更是如此。开始的收益往往是很低的,不过随着时间的增长会慢慢变好。也就是说,对于一个金融顾问 i,他设计的经营方案中,每天的收益都比前一天高,并且均增长一个相同的量 Pi。
由于金融顾问的工作效率不高,**所以在特定的时间,Blue Mary 只能根据他已经得到的经营方案来估算某一时间的最大收益。**由于 Blue Mary 是很没有经济头脑的,所以他在估算每天的最佳获益时完全不会考虑之前的情况,而是直接从所有金融顾问的方案中选择一个在当天获益最大的方案的当天的获益值,例如:
有如下两个金融顾问分别对前四天的收益方案做了设计:
第一天 | 第二天 | 第三天 | 第四天 | Pi | |
---|---|---|---|---|---|
顾问 1 | 1 | 5 | 9 | 13 | 4 |
顾问 2 | 2 | 5 | 8 | 11 | 3 |
在第一天,Blue Mary认为最大收益是 2(使用顾问 2 的方案),而在第三天和第四天,他认为最大收益分别是 9 和 13(使用顾问 1 的方案)。而他认为前四天的最大收益是:
2 + 5 + 9 + 13 = 292+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 行,每行开头一个单词Query
或Project
。
若单词为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≤100000,1≤T≤50000,0<P<100,∣S∣≤10^5
提示:
本题读写数据量可能相当巨大,请选手注意选择高效的文件读写方式。
就是上道题的具象化,现在对对李超线段树也有了更好的理解,就是记录每个区间的最优线段,如果有交叉,谁中点高归谁,然后在另一半继续更新最优线段。
查找的时候也是,查出所有带x坐标的点,求最大值。
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 1e5 + 5;
double k[200010], b[200010];
int tag[200010], cnt = 0;
inline int ls(int x){
return x << 1;
}
inline int rs(int x){
return x << 1 | 1;
}
inline double w(int id, int x){
return k[id] * (x - 1) + b[id];
}
void add(int root, ll l, ll r, int x){
if(w(x, l) > w(tag[root], l) && w(x, r) > w(tag[root], r)){
tag[root] = x;
return;
}
if(w(x, l) <= w(tag[root], l) && w(x, r) <= w(tag[root], r)){
return;
}
int mid = (l + r) >> 1;
if(k[tag[root]] < k[x]){
if(w(x,mid) > w(tag[root],mid)){
add(ls(root), l, mid, tag[root]);
tag[root] = x;
}
else{
add(rs(root), mid + 1, r, x);
}
}
else{
if(w(x,mid) > w(tag[root],mid)){
add(rs(root), mid + 1, r, tag[root]);
tag[root] = x;
}
else{
add(ls(root), l, mid, x);
}
}
}
double getAns(int x, int p, ll l, ll r){
if(l == r){
return w(tag[p], x);
}
int mid = (l + r) >> 1;
if(x <= mid){
return max(w(tag[p], x), getAns(x, ls(p), l, mid));
}
else{
return max(w(tag[p], x), getAns(x, rs(p), mid + 1, r));
}
}
int main()
{
ios::sync_with_stdio(false);
ll n;
scanf("%lld", &n);
for(int i = 1; i <= n; i++){
char s[10];
scanf("%s", s);
if(s[0] == 'P'){
cnt++;
scanf("%lf%lf", &b[cnt], &k[cnt]);
add(1, 1, 50005, cnt);
}
else{
int t;
scanf("%d", &t);
cout << int(getAns(t, 1, 1,50005) / 100)<<endl;
}
}
return 0;
}
3、[CEOI2017]Building Bridges
题目描述
有 n 根柱子依次排列,每根柱子都有一个高度。第 i 根柱子的高度为 hi。
现在想要建造若干座桥,如果一座桥架在第 ii 根柱子和第 jj 根柱子之间,那么需要(hi−hj)^2 的代价。
在造桥前,所有用不到的柱子都会被拆除,因为他们会干扰造桥进程。第 i 根柱子被拆除的代价为 wi,注意 wi 不一定非负,因为可能政府希望拆除某些柱子。
现在政府想要知道,通过桥梁把第 1 根柱子和第 n 根柱子连接的最小代价。注意桥梁不能在端点以外的任何地方相交。
输入格式
第一行一个正整数 n。
第二行 n 个空格隔开的整数,依次表示 h1,h2,⋯,hn。
第三行 n 个空格隔开的整数,依次表示 w1,w2,⋯,wn。
输出格式
输出一行一个整数表示最小代价,注意最小代价不一定是正数。
输入输出样例
输入 #1
6
3 8 7 1 6 6
0 -1 9 1 2 0
输出 #1
17
说明/提示
对于 100% 的数据,有 2≤n≤105;0≤*hi*,∣*wi*∣≤106。
这题是找的李超线段树,但是这个推理感觉自己还是推不出来,但这个写出来之后后面就是模板了。
AC code
#include <bits/stdc++.h>
//#pragma GCC optimize(3,"Ofast","inline")
#define re register
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=1e5+9, M=1e6+9;
ll a[N], b[N], h[N], w[N], f[N];
int s[M<<2], u;
inline int ls(int x){
return x << 1;
}
inline int rs(int x){
return x << 1 | 1;
}
inline ll g(int i, int x){
return b[i] + a[i] * x;
}
//速读
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void upd(int k, int l, int r, int t){
if(l==r){
if(g(t, l)<g(s[k], l))s[k]=t;
return;
}
int m=l+r>>1;
if(g(t, m)<g(s[k], m))swap(t,s[k]);
if(g(t, l)<g(s[k], l))upd(k<<1,l,m,t);
else if(g(t, r)<g(s[k], r))upd(k<<1|1,m+1,r,t);
}
ll qry(int k, int l, int r){
if(l == r){
return g(s[k], u);
}
int mid = (l + r) >> 1;
return min(g(s[k], u), u <= mid ? qry(ls(k), l, mid) : qry(rs(k), mid + 1, r));
}
int main()
{
ios::sync_with_stdio(false);
int n;
scanf("%d", &n);
b[0] = 1e18;
for(int i = 1; i <= n; i++){
scanf("%lld", h + i);
}
for(int i = 1; i <= n; i++){
scanf("%lld", w + i);
w[i] += w[i - 1];
}
a[1] = -2 * h[1];
b[1] = h[1] * h[1] - w[1];
upd(1, 0, M, 1);
for(int i = 2; i <= n; i++){
u = h[i];
f[i] = h[i] * h[i] + w[i - 1] + qry(1, 0, M);
a[i] = -2 * h[i];
b[i] = f[i] + h[i] * h[i] - w[i];
upd(1, 0, M, i);
}
printf("%lld", f[n]);
return 0;
}
三、总结
李超线段树的板子到这里也掌握得差不多了,主要还是用的时候要能想到,就是求区间的最大优势线段,判断两个直线关系,找中点,判断中点的优势关系等等。