【做题笔记】线段树
正在更新中~
P3870 开关
题目描述
现有 \(n\) 盏灯排成一排,从左到右依次编号为:\(1\),\(2\),……,\(n\)。然后依次执行 \(m\) 项操作。
操作分为两种:
- 指定一个区间 \([a,b]\),然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
- 指定一个区间 \([a,b]\),要求你输出这个区间内有多少盏灯是打开的。
灯在初始时都是关着的。
题解
区间修改+区间查询,不难想到用线段树维护区间打开灯的数量。
修改时,将开着的灯关闭,关着的灯打开,也就是将开着灯的数目和没开灯的数目交换,在线段树上表现为将区间长度减去原区间打开的数量。
区间查询就是一个基础的线段树查询区间和。
增添标记时,由于对于一段同一段区间修改两次,开着的灯并不会改变,考虑用异或来维护。异或的性质是相同返回 0,不同返回 1,与之前的区间性质相同,故可以用它来维护。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int tree[N<<4];
int tag[N<<4];
void pushup(int cur){
tree[cur]=tree[cur<<1]+tree[cur<<1|1];
}
void addtag(int cur,int lt,int rt){
tag[cur]^=1;//标记与之前值异或,并且更改此时区间的值
tree[cur]=rt-lt+1-tree[cur];//现在开着的灯=之前关着的灯
}
//下面的都是板子
void pushdown(int cur,int lt,int rt){
if(tag[cur]==0) return;
int mid=(lt+rt)>>1;
addtag(cur<<1,lt,mid);
addtag(cur<<1|1,mid+1,rt);
tag[cur]=0;
}
void update(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return;
if(lt>=qx&&rt<=qy){
addtag(cur,lt,rt);
return;
}
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,qx,qy);
update(cur<<1|1,mid+1,rt,qx,qy);
pushup(cur);
}
int query(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return 0;
if(lt>=qx&&rt<=qy) return tree[cur];
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
return query(cur<<1,lt,mid,qx,qy)+query(cur<<1|1,mid+1,rt,qx,qy);
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;//不需要建树是因为数组一开始都是 0,按我的写法就不需要建树了
while(m--){
int opt,x,y;
cin>>opt>>x>>y;
if(opt==0) update(1,1,n,x,y);
else cout<<query(1,1,n,x,y)<<"\n";
}
return 0;
}
P2023 维护序列
题面描述
有一个长为 \(n\) 的数列 \(\{a_n\}\),有如下三种操作形式:
- 格式
1 t g c
,表示把所有满足 \(t\le i\le g\) 的 \(a_i\) 改为 \(a_i\times c\) ; - 格式
2 t g c
表示把所有满足 \(t\le i\le g\) 的 \(a_i\) 改为 \(a_i+c\) ; - 格式
3 t g
询问所有满足 \(t\le i\le g\) 的 \(a_i\) 的和,由于答案可能很大,你只需输出这个数模 \(p\) 的值。
题解
【模板】线段树2 的双倍经验题,一些区间乘的细节放代码里了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,mod,a[N];
int tree[N<<2];
int mtag[N<<2],atag[N<<2];
void pushup(int cur){
tree[cur]=(tree[cur<<1]%mod+tree[cur<<1|1]%mod)%mod;//多模一些,防止爆 long long
}
void addtag_add(int cur,int lt,int rt,int k){
tree[cur]=(tree[cur]+k*(rt-lt+1)%mod)%mod;
atag[cur]=(atag[cur]+k)%mod;
}
void addtag_mul(int cur,int lt,int rt,int k){
tree[cur]=(tree[cur]*k)%mod;
mtag[cur]=(mtag[cur]*k)%mod;
atag[cur]=(atag[cur]*k)%mod;//乘法会把加法的标记也会相乘
}
void pushdown(int cur,int lt,int rt){
int mid=(lt+rt)>>1;
if(mtag[cur]!=1){//!注意,pushdown的顺序是先乘后加!
addtag_mul(cur<<1,lt,mid,mtag[cur]);
addtag_mul(cur<<1|1,mid+1,rt,mtag[cur]);
mtag[cur]=1;//乘法标记初始是1
}
if(atag[cur]!=0){
addtag_add(cur<<1,lt,mid,atag[cur]);
addtag_add(cur<<1|1,mid+1,rt,atag[cur]);
atag[cur]=0;
}
}
void build(int cur,int lt,int rt){
mtag[cur]=1;
if(lt==rt){
tree[cur]=a[lt]%mod;
return;
}
int mid=(lt+rt)>>1;
build(cur<<1,lt,mid);
build(cur<<1|1,mid+1,rt);
pushup(cur);
}
void update_add(int cur,int lt,int rt,int qx,int qy,int k){
if(lt>qy||rt<qx) return;
if(lt>=qx&&rt<=qy){
addtag_add(cur,lt,rt,k);
return;
}
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
update_add(cur<<1,lt,mid,qx,qy,k);
update_add(cur<<1|1,mid+1,rt,qx,qy,k);
pushup(cur);
}
void update_mul(int cur,int lt,int rt,int qx,int qy,int k){
if(lt>qy||rt<qx) return;
if(lt>=qx&&rt<=qy){
addtag_mul(cur,lt,rt,k);
return;
}
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
update_mul(cur<<1,lt,mid,qx,qy,k);
update_mul(cur<<1|1,mid+1,rt,qx,qy,k);
pushup(cur);
}
int query(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return 0;
if(lt>=qx&&rt<=qy) return tree[cur]%mod;
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
return (query(cur<<1,lt,mid,qx,qy)+query(cur<<1|1,mid+1,rt,qx,qy))%mod;
}
signed main(){
ios::sync_with_stdio(false);
int q;
cin>>n>>mod;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
cin>>q;
while(q--){
int type,x,y;
cin>>type>>x>>y;
if(type==1){
int k;
cin>>k;
update_mul(1,1,n,x,y,k%mod);
}
else if(type==2){
int k;
cin>>k;
update_add(1,1,n,x,y,k%mod);
}
else cout<<query(1,1,n,x,y)<<"\n";
}
return 0;
}
P5057 简单题
题目描述
有一个 \(n\) 个元素的数组,每个元素初始均为 0。有 \(m\) 条指令,要么让其中一段连续序列数字反转——0 变 1,1
变 0(操作 1),要么询问某个元素的值(操作 2)。
题解
这里其实理论上来说甚至没有树,也不需要通过线段树维护什么东西。只是简单的运用了线段树的思想。
反转依旧可以使用异或,如果是一段区间的话就给这段区间打上标记,如果已经到了叶子节点,直接将原数组异或一下即可。
询问还是需要一个函数,不能直接输出数组下标表示的位置。这是因为在这个叶子节点上,可能还有没下传的标记。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int a[N];
int tag[N<<4];
void addtag(int cur,int lt,int rt){
if(lt==rt) a[lt]^=1;
else tag[cur]^=1;
}
void pushdown(int cur,int lt,int rt){
if(tag[cur]==0) return;
int mid=(lt+rt)>>1;
addtag(cur<<1,lt,mid);
addtag(cur<<1|1,mid+1,rt);
tag[cur]=0;
}
void update(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return;
if(lt>=qx&&rt<=qy){
addtag(cur,lt,rt);
return;
}
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,qx,qy);
update(cur<<1|1,mid+1,rt,qx,qy);
}
int query(int cur,int lt,int rt,int x){
if(lt>x||rt<x) return 0;
if(lt==x&&rt==x) return a[lt];
pushdown(cur,lt,rt);
int mid=(lt+rt)>>1;
return query(cur<<1,lt,mid,x)+query(cur<<1|1,mid+1,rt,x);
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
while(m--){
int opt,x,y;
cin>>opt>>x;
if(opt==1){
cin>>y;
update(1,1,n,x,y);
}
else cout<<query(1,1,n,x)<<"\n";
}
return 0;
}
P1531 I Hate It
题目描述
当 \(c\) 为 Q
的时候,表示这是一条询问操作,它询问 ID 从 \(a\) 到 \(b\)(包括 \(a,b\)) 的学生当中,成绩最高的是多少。
当 \(c\) 为 U
的时候,表示这是一条更新操作,如果当前 \(a\) 学生的成绩低于 \(b\),则把 ID 为 \(a\) 的学生的成绩更改为 \(b\),否则不改动。
题解
区间询问最大值+单点修改。
似乎好像就是一个线段树维护最大值的板子,感觉没什么特别需要注意的,甚至不要打标记。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m;
int a[N];
int tree[N<<4];
void pushup(int cur){
tree[cur]=max(tree[cur<<1],tree[cur<<1|1]);//维护最大值
}
void build(int cur,int lt,int rt){
if(lt==rt){
tree[cur]=a[lt];
return;
}
int mid=(lt+rt)>>1;
build(cur<<1,lt,mid);
build(cur<<1|1,mid+1,rt);
pushup(cur);
}
void update(int cur,int lt,int rt,int x,int k){
if(lt>x||rt<x) return;
if(lt==x&&rt==x){
tree[cur]=max(tree[cur],k);//如果小于k的话才更新
return;
}
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,x,k);
update(cur<<1|1,mid+1,rt,x,k);
pushup(cur);
}
int query(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return 0;
if(lt>=qx&&rt<=qy) return tree[cur];
int mid=(lt+rt)>>1;
return max(query(cur<<1,lt,mid,qx,qy),query(cur<<1|1,mid+1,rt,qx,qy));
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);//注意要建树
while(m--){
char c;
int x,y;
cin>>c>>x>>y;
if(c=='Q') cout<<query(1,1,n,x,y)<<"\n";
else update(1,1,n,x,y);
}
return 0;
}
P2184 贪婪大陆
题面描述
小 FF 最后一道防线是一条长度为 \(n\) 的战壕,小 FF 拥有无数多种地雷,而 SCV 每次可以在 \([L, R]\) 区间埋放同一种不同于之前已经埋放的地雷。由于情况已经十万火急,小 FF 在某些时候可能会询问你在 \([L',R']\) 区间内有多少种不同的地雷,他希望你能尽快的给予答复。
题解
\(\tiny\text{难度一下子上来了一点不是吗}\)
由于每一次埋的地雷都是不同种类的,所以题意可以简化成询问一段区间内有多少种不同的区间。
考虑使用线段树,维护一段区间内含有的不同左端点和右端点。记录目前为止一共有多少种地雷,查询时在 \((1,l-1)\) 中右端点的数量,以及在 \((r+1,n)\) 的左端点数量。显然,区间 \((l,r)\) 一定不包含前面所统计的那些区间,用总地雷数量减去不包含的数量,得到的便是包含的数量。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
int tag[N<<4];
int cnt=0;
struct node{
int l,r;
}tree[N<<4];
void pushup(int cur){
tree[cur].l=tree[cur<<1].l+tree[cur<<1|1].l;
tree[cur].r=tree[cur<<1].r+tree[cur<<1|1].r;
}
void update(int cur,int lt,int rt,int x,int opt){
if(lt>x||rt<x) return;
if(lt==x&&rt==x){
if(opt==-1) tree[cur].l++;
else tree[cur].r++;
return;
}
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,x,opt);
update(cur<<1|1,mid+1,rt,x,opt);
pushup(cur);
}
int query(int cur,int lt,int rt,int qx,int qy,int opt){
if(lt>qy||rt<qx) return 0;
if(lt>=qx&&rt<=qy){
if(opt==-1) return tree[cur].l;
else return tree[cur].r;
}
int mid=(lt+rt)>>1;
return query(cur<<1,lt,mid,qx,qy,opt)+query(cur<<1|1,mid+1,rt,qx,qy,opt);
}
int main(){
// freopen("1.in","r",stdin);
ios::sync_with_stdio(false);
cin>>n>>m;
while(m--){
int opt,x,y;
cin>>opt>>x>>y;
if(opt==1){
update(1,1,n,x,-1);//-1代表左端点,1代表右端点,下面同理
update(1,1,n,y,1);
cnt++;
}
else{
int sum=0;
if(x!=1) sum+=query(1,1,n,1,x-1,1);
if(y!=n) sum+=query(1,1,n,y+1,n,-1);
cout<<cnt-sum<<"\n";
}
}
return 0;
}
P4588 数学计算
题面描述
小豆现在有一个数 \(x\),初始值为 \(1\)。小豆有 \(Q\) 次操作,操作有两种类型:
1 m
:将 \(x\) 变为 \(x \times m\),并输出 \(x \bmod M\)
2 pos
:将 \(x\) 变为 \(x\) 除以第 \(pos\) 次操作所乘的数(保证第 \(pos\) 次操作一定为类型 1,对于每一个类型 1 的操作至多会被除一次),并输出 \(x \bmod M\)。
题解
这道题不像之前的题,线段树维护的东西比较难看出,不错的思维题。
先观察题目,每次操作二只会除以一个数,等效于除这个数之外的所有数相乘。
于是我们考虑以时间为叶子节点建立线段树,操作一便将第 t 个节点变为 m,否则将第 t 个节点变为 1。并且用线段树维护总的区间乘。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int mod;
int tree[N<<2];
void pushup(int cur){
tree[cur]=(tree[cur<<1]*tree[cur<<1|1])%mod;
}
void build(int cur,int lt,int rt){
if(lt==rt){
tree[cur]=1;
return;
}
int mid=(lt+rt)>>1;
build(cur<<1,lt,mid);
build(cur<<1|1,mid+1,rt);
pushup(cur);
}
void update(int cur,int lt,int rt,int qx,int k){
if(lt>qx||rt<qx) return;
if(lt==qx&&rt==qx){
tree[cur]=k%mod;
return;
}
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,qx,k);
update(cur<<1|1,mid+1,rt,qx,k);
pushup(cur);
}
signed main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
memset(tree,0,sizeof(tree));
int n;
cin>>n>>mod;
build(1,1,n);
for(int i=1;i<=n;i++){
int type,x;
cin>>type>>x;
if(type==1) update(1,1,n,i,x);
else update(1,1,n,x,1);
cout<<tree[1]<<"\n";
}
}
return 0;
}
P3369 普通平衡树
题面描述
- 插入 \(x\) 数
- 删除 \(x\) 数(若有多个相同的数,因只删除一个)
- 查询 \(x\) 数的排名(排名定义为比当前数小的数的个数 \(+1\) )
- 查询排名为 \(x\) 的数
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)
题解
权值线段树模板题,相信如果您看了题单简介里的博客的话,这道题一定可以秒掉。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
int b[N],tot;
int tree[N<<4];//注意权值线段树维护的是数的出现次数,也就是桶
struct node{
int opt,x;
}a[N];
void pushup(int cur){
tree[cur]=tree[cur<<1]+tree[cur<<1|1];
}
//简单的单点修改
void update(int cur,int lt,int rt,int x,int k){
if(lt>x||rt<x) return;
if(lt==x&&rt==x){
tree[cur]+=k;
return;
}
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,x,k);
update(cur<<1|1,mid+1,rt,x,k);
pushup(cur);
}
//通过数来查询排名,普通的序列和
int query_r(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return 0;
if(lt>=qx&&rt<=qy) return tree[cur];
int mid=(lt+rt)>>1;
return query_r(cur<<1,lt,mid,qx,qy)+query_r(cur<<1|1,mid+1,rt,qx,qy);
}
//通过排名来查询数,即区间第 x 小的数
int query_n(int cur,int lt,int rt,int x){
if(lt==rt) return lt;
int mid=(lt+rt)>>1;
if(tree[cur<<1]>=x) return query_n(cur<<1,lt,mid,x);//二分的思想
else return query_n(cur<<1|1,mid+1,rt,x-tree[cur<<1]);//记得要减去左区间的长度
}
void solve(int opt,int x){
if(opt==1) update(1,1,tot,x,1);//插入
else if(opt==2) update(1,1,tot,x,-1);//删除
else if(opt==3){
//查询排名
if(x==1){
//特判,否则下面的右端点会比左端点小
cout<<"1\n";
return;
}
cout<<query_r(1,1,tot,1,x-1)+1<<"\n";//x的排名为所有比它小的数+1,在线段树上等价于区间 1~x-1 的和
}
else if(opt==4){
//查询排名为 x 的数
cout<<b[query_n(1,1,tot,x)]<<"\n"; //需要返回原数组输出
}
else if(opt==5){
//前驱
int tmp=query_r(1,1,tot,1,x-1);//先查询最后一个比 x 小的数的排名
cout<<b[query_n(1,1,tot,tmp)]<<"\n";//再通过排名来找数
}
else if(opt==6){
//后继
int tmp=query_r(1,1,tot,1,x)+1;//先查询第一个比 x 大的数的排名
cout<<b[query_n(1,1,tot,tmp)]<<"\n";//再通过排名来找数
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].opt>>a[i].x;
if(a[i].opt!=4) b[++tot]=a[i].x;//排名无需离散化
}
sort(b+1,b+tot+1);
tot=unique(b+1,b+tot+1)-b-1;
for(int i=1;i<=n;i++){
int opt=a[i].opt,x=a[i].x;
if(opt!=4) x=lower_bound(b+1,b+tot+1,a[i].x)-b;
solve(opt,x);
}
return 0;
}
P6186 冒泡排序
题面描述
给定一个 \(1 ∼ n\) 的排列 \(p_i\),接下来有 \(m\) 次操作,操作共两种:
- 交换操作:给定 \(x\),将当前排列中的第 \(x\) 个数与第 \(x+1\) 个数交换位置。
- 询问操作:给定 \(k\),请你求出当前排列经过 \(k\) 轮冒泡排序后的逆序对个数。
对一个长度为 \(n\) 的排列 \(p_i\) 进行一轮冒泡排序的伪代码如下:
for i = 1 to n-1:
if p[i] > p[i + 1]:
swap(p[i], p[i + 1])
题解
- 先考虑不进行交换位置的逆序对。
不妨来手玩一下数据:4 2 3 5 1。
定义数组 \(b_{[i]}\) 表示在 \(i\) 左边且比 \(a_{[i]}\) 大的,此时:
位置 \(i\) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
\(b_{[i]}\) | 4 | 1 | 1 | 0 | 0 |
第一轮:2 3 4 1 5
位置 \(i\) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
\(b_{[i]}\) | 3 | 0 | 0 | 0 | 0 |
第二轮:2 3 1 4 5
位置 \(i\) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
\(b_{[i]}\) | 2 | 0 | 0 | 0 | 0 |
第三轮:2 1 3 4 5
位置 \(i\) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
\(b_{[i]}\) | 1 | 0 | 0 | 0 | 0 |
第四轮:1 2 3 4 5
位置 \(i\) | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
\(b_{[i]}\) | 0 | 0 | 0 | 0 | 0 |
很明显,每进行一轮冒泡排序,每个大于 \(0\) 的 \(b_{[i]}\) 都会减一。
这样会减小的逆序对数就是所有 \(b_{[i]}\) 大于 \(0\) 的个数。
这样的话记录一下最开始的逆序对数,每冒泡排序一次就减去这一轮消耗的逆序对数即可。
- 再考虑进行交换的逆序对数
设现在交换的是 \(a_{[x]}\) 和 \(a_{[i+1]}\)。
这时候只有这两个数的逆序对数会受到影响,再进行一下分类讨论。
如果 \(a_{[x]} < a_{[x+1]}\),很明显,现在的 \(b_{[x+1]}\) 会加一。同理,当情况反过来的时候,现在的 \(b_{[x]}\) 将会减一。
这样我们就可以通过线段树或树状数组来维护答案啦!
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m;
int a[N];
int b[N];
int tre[N<<2];
struct node{
int num,sum;//num存大于0的个数,sum存答案
}tree[N<<4];
//树状数组部分
inline int lowbit(int x){return x&(-x);}
inline void upd(int x,int k){
for(int i=x;i<=n;i+=lowbit(i)) tre[i]+=k;
}
inline int que(int x){
int sum=0;
for(int i=x;i>=1;i-=lowbit(i)) sum+=tre[i];
return sum;
}
//权值线段树部分
inline void pushup(int cur){
tree[cur].num=tree[cur<<1].num+tree[cur<<1|1].num;
tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
}
void update(int cur,int lt,int rt,int x,int k){
if(lt>x||rt<x) return;
if(lt==x&&rt==x){
tree[cur].num+=k;
tree[cur].sum+=lt*k;
return;
}
int mid=(lt+rt)>>1;
update(cur<<1,lt,mid,x,k);
update(cur<<1|1,mid+1,rt,x,k);
pushup(cur);
}
int query(int cur,int lt,int rt,int qx,int qy){
if(lt>qy||rt<qx) return 0;
if(lt>=qx&&rt<=qy) return tree[cur].sum-tree[cur].num*(qx-1);
int mid=(lt+rt)>>1;
return query(cur<<1,lt,mid,qx,qy)+query(cur<<1|1,mid+1,rt,qx,qy);
}
void solve(int x){
if(a[x]==a[x+1]) return;
update(1,1,n,b[x],-1);
update(1,1,n,b[x+1],-1);
int u=b[x],v=b[x+1];
if(a[x]>a[x+1]){//按照之前的分类讨论
b[x]=v-1;
b[x+1]=u;
}
else{
b[x]=v;
b[x+1]=u+1;
}
update(1,1,n,b[x],1);
update(1,1,n,b[x+1],1);
swap(a[x],a[x+1]);//a数组也要交换
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=i-que(a[i])-1;//用树状数组维护最开始的逆序对数
upd(a[i],1);
update(1,1,n,b[i],1);//并且在线段树上更新
}
while(m--){
int opt,x;
cin>>opt>>x;
if(opt==1) solve(x);
else{
if(x>=n){//特判一下防止越界
cout<<"0\n";
continue;
}
cout<<query(1,1,n,x+1,n)<<"\n";//x轮后的逆序对数就是所有b[i]大于x的和
}
}
return 0;
}
P4254 Blue Mary开公司
题目描述
每次有两种操作,第一种操作为查询直线 \(x=t\) 与整个函数图像交点纵坐标的最大值;第二种操作为插入一条直线 \(y=kx+b\)。
题解
李超线段树板子,详细可参照题目题解或者题单中的博客,本题的一些细节就放进代码里了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=5e4+5;
int n;
int tree[M<<4];
double k[N],b[N];
double f(int x,int cur){return k[cur]*(x-1)+b[cur];}//函数值
double max(double x,double y){
if(x<y) return y;
else return x;
}
void update(int cur,int lt,int rt,int x){
if(lt==rt){//如果是叶子节点,同下面也无需判断lt=x
if(f(lt,x)>f(lt,tree[cur])) tree[cur]=x;
return;
}
int mid=(lt+rt)>>1;
if(k[x]>k[tree[cur]]){//这一重点部分建议画图理解
if(f(mid,x)>f(mid,tree[cur])){
update(cur<<1,lt,mid,tree[cur]);
tree[cur]=x;
}
else update(cur<<1|1,mid+1,rt,x);
}
else if(k[x]<k[tree[cur]]){
if(f(mid,x)>f(mid,tree[cur])){
update(cur<<1|1,mid+1,rt,tree[cur]);
tree[cur]=x;
}
else update(cur<<1,lt,mid,x);
}
}
double query(int cur,int lt,int rt,int qx){//注意这里是double
if(lt>qx||rt<qx) return 0;
if(lt==rt) return f(qx,tree[cur]);
int mid=(lt+rt)>>1;
return max(f(qx,tree[cur]),max(query(cur<<1,lt,mid,qx),query(cur<<1|1,mid+1,rt,qx)));
}
int main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
string s;
cin>>s;
if(s[0]=='Q'){
int x;
cin>>x;
cout<<(int)query(1,1,M,x)/100<<"\n";
}
else{
double x,y;
cin>>x>>y;
k[++n]=y,b[n]=x;//注意k,b的顺序
update(1,1,M,n);
}
}
return 0;
}