【做题笔记】线段树

题单

正在更新中~

P3870 开关

题目

题目描述

现有 \(n\) 盏灯排成一排,从左到右依次编号为:\(1\)\(2\),……,\(n\)。然后依次执行 \(m\) 项操作。

操作分为两种:

  1. 指定一个区间 \([a,b]\),然后改变编号在这个区间内的灯的状态(把开着的灯关上,关着的灯打开);
  2. 指定一个区间 \([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. 格式 1 t g c,表示把所有满足 \(t\le i\le g\)\(a_i\) 改为 \(a_i\times c\) ;
  2. 格式 2 t g c 表示把所有满足 \(t\le i\le g\)\(a_i\) 改为 \(a_i+c\) ;
  3. 格式 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 普通平衡树

题面描述

  1. 插入 \(x\)
  2. 删除 \(x\) 数(若有多个相同的数,因只删除一个)
  3. 查询 \(x\) 数的排名(排名定义为比当前数小的数的个数 \(+1\) )
  4. 查询排名为 \(x\) 的数
  5. \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)
  6. \(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\) 次操作,操作共两种:

  1. 交换操作:给定 \(x\),将当前排列中的第 \(x\) 个数与第 \(x+1\) 个数交换位置。
  2. 询问操作:给定 \(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;
}
posted @ 2022-08-17 15:18  Cloote  阅读(52)  评论(2编辑  收藏  举报