洛谷每日一练5.9--P4513+P2824(线段树)

P4513

题意:

给 n 个 元 素 , 带 单 点 修 改 操 作 , 每 次 询 问 区 间 内 长 度 至 少 为 1 的 给n个元素,带单点修改操作,每次询问区间内长度至少为1的 n1
最 大 连 续 子 段 和 是 多 少 ? 最大连续子段和是多少?

思路:

与 U V a 3938 类 似 与UVa3938 类似 UVa3938
用 线 段 树 维 护 , 把 区 间 当 成 节 点 , 维 护 这 个 节 点 的 ( 长 度 至 少 为 1 的 以 下 均 省 略 ) 用线段树维护,把区间当成节点,维护这个节点的(长度至少为1的以下均省略) 线(1)
最 大 前 缀 和 、 最 大 后 缀 、 区 间 和 、 区 间 最 大 连 续 子 段 和 最大前缀和、最大后缀、区间和、区间最大连续子段和
设 为 m x s u b 、 m x s u f 、 s u m 、 a n s 设为mxsub、mxsuf、sum、ans mxsubmxsufsumans
对 于 一 个 节 点 的 最 大 前 缀 和 : 要 么 为 左 子 节 点 的 m x s u b , 要 么 为 左 子 节 点 的 s u m + 右 子 节 点 的 m x s u b 对于一个节点的最大前缀和:要么为左子节点的mxsub,要么为左子节点的sum+右子节点的mxsub mxsubsum+mxsub
对 于 一 个 节 点 的 最 大 后 缀 和 : 要 么 为 右 子 节 点 的 m x s u f , 要 么 为 右 子 节 点 的 s u m + 左 子 节 点 的 m x s u b 对于一个节点的最大后缀和:要么为右子节点的mxsuf,要么为右子节点的sum+左子节点的mxsub mxsufsum+mxsub
对 于 一 个 节 点 的 最 大 连 续 区 间 和 要 么 为 左 子 节 点 的 a n s , 要 么 为 右 子 节 点 的 a n s , 要 么 为 左 的 m x s u f + 右 的 m x s u b 对于一个节点的最大连续区间和要么为左子节点的ans,要么为右子节点的ans,要么为左 的mxsuf + 右的mxsub ansansmxsuf+mxsub
还 有 一 个 坑 点 : 询 问 的 区 间 不 一 定 L < = R , Q A Q 还有一个坑点:询问的区间不一定L <= R,QAQ L<=RQAQ

#include <cstdio>
#include <algorithm>
#include <iostream> 
using namespace std;
typedef long long ll;
const int N = 5e5+10;
int n,m;
ll a[N];
struct Node{
	int l,r;
	//该节点的最大前缀,该节点的最大后缀,区间和,该节点的最大连续区间和 
	ll mx_sub,mx_suf,sum,ans;
}tr[N<<2];
void pushup(Node& root,Node& ls,Node& rs){
	root.sum = ls.sum + rs.sum;
	root.mx_sub = max(ls.mx_sub,ls.sum+rs.mx_sub);
	root.mx_suf = max(rs.mx_suf,rs.sum + ls.mx_suf);
	root.ans = max(max(ls.ans,rs.ans),ls.mx_suf+rs.mx_sub);
}
void build(int p,int l,int r){
	tr[p].l = l,tr[p].r = r;
	if(l == r){
		tr[p].mx_sub = tr[p].mx_suf = tr[p].ans = tr[p].sum = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(tr[p],tr[p<<1],tr[p<<1|1]);
}
//单点修改,把 a[x] 修改为 y.
void update(int p,int x,int y){
	if(tr[p].l == tr[p].r){
		tr[p].sum = tr[p].ans = tr[p].mx_sub = tr[p].mx_suf = y;
		return; 
	}
	int mid = tr[p].l + tr[p].r >> 1;
	if(x <= mid) update(p<<1,x,y);
	else update(p<<1|1,x,y);
	pushup(tr[p],tr[p<<1],tr[p<<1|1]);
}
//询问[L,R]的最大连续子段和 
Node query(int p,int L,int R){
	int l = tr[p].l,r = tr[p].r;
	if(l >= L&&R >= r) return tr[p];
	int mid = l + r >> 1;
	if(R <= mid) return query(p<<1,L,R);
	else if(L > mid) return query(p<<1|1,L,R);
	else{
		Node ls = query(p<<1,L,R);
		Node rs = query(p<<1|1,L,R);
		Node res;
		pushup(res,ls,rs);
		return res;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) scanf("%lld",a+i);
	build(1,1,n);
	while(m--){
		int op;scanf("%d",&op);
		if(op == 1){
			int l,r;scanf("%d%d",&l,&r);
			if(l > r) swap(l,r);
			printf("%lld\n",query(1,l,r).ans);
		}else{
			int x,y;scanf("%d%d",&x,&y);
			update(1,x,y);
		}
	}
	return 0;
} 

P2824

题意:

给 出 一 个 1 到 n 的 排 列 , 然 后 每 次 进 行 一 次 局 部 排 列 , 可 能 升 序 排 , 可 能 降 序 排 给出一个1到n的排列,然后每次进行一次局部排列,可能升序排,可能降序排 1n
最 后 来 一 个 询 问 , 问 位 置 p 是 什 么 数 字 最后来一个询问,问位置p是什么数字 p

思路:

这 题 的 二 分 想 法 太 女 少 了 这题的二分想法太女少了
对 于 c h e c k 一 个 x , 对 于 一 个 原 来 排 列 相 较 于 x 变 成 一 个 01 串 , 大 于 等 于 x 为 1 , 小 于 x 为 0 对于check一个x,对于一个原来排列相较于x变成一个01串,大于等于x为1,小于x为0 checkxx01x1x0
例 如 : a i : 1 例如:a_i : 1 ai:1 4 4 4 2 2 2 3 3 3 5 5 5, 相 较 与 3 变 成 : b i : 0 相较与3变成:b_i:0 3bi:0 1 1 1 0 0 0 1 1 1 1 1 1
然 后 局 部 排 序 就 是 询 问 该 区 间 有 多 少 个 1 , 如 果 升 序 就 把 后 面 的 全 修 改 为 1 , 降 序 则 将 前 面 修 改 然后局部排序就是询问该区间有多少个1,如果升序就把后面的全修改为1,降序则将前面修改 11
全 部 局 部 排 序 完 成 后 , 检 查 p 位 置 是 1 还 是 0 全部局部排序完成后,检查p位置是1还是0 p10
这 个 有 啥 用 ? 这个有啥用?
因 为 相 较 于 x 将 其 变 成 01 串 再 去 局 部 排 序 完 毕 后 , 不 影 响 p 位 置 这 个 值 与 x 的 相 对 大 小 因为相较于x将其变成01串再去局部排序完毕后,不影响p位置这个值与x的相对大小 x01px
换 句 话 讲 , 如 果 p 位 置 是 1 , 那 么 按 正 常 局 部 排 序 后 a p 是 满 足 a p > = x 的 换句话讲,如果p位置是1,那么按正常局部排序后a_p是满足a_p >= x的 p1apap>=x
既 然 这 样 我 们 一 直 二 分 找 到 最 大 的 x 既然这样我们一直二分找到最大的x x

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1e5+10;
int n,m,q,a[N];
int read(){
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
    return f*x;
}
struct Node{
	int l,r,lazy,sum;
}tr[N<<2];
//操作类型、操作区间 
struct node{
	int opt,l,r;
}op[N];
void pushup(int p){
	tr[p].sum = tr[p<<1].sum + tr[p<<1|1].sum;
}
void build(int p,int l,int r){
	tr[p].lazy = -1,tr[p].sum = 0;
	tr[p].l = l,tr[p].r = r;
	if(l == r) return;
	int mid = l + r >> 1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p); 
}
void pushdown(int p){
	if(tr[p].lazy != -1){
		tr[p<<1].lazy = tr[p<<1|1].lazy = tr[p].lazy;
		tr[p<<1].sum = (tr[p<<1].r - tr[p<<1].l + 1)*tr[p].lazy;
		tr[p<<1|1].sum = (tr[p<<1|1].r - tr[p<<1|1].l + 1)*tr[p].lazy;
		tr[p].lazy = -1;
	}
}
void update(int p,int L,int R,int dx){
	if(L > R) return;
	int l = tr[p].l,r = tr[p].r;
	if(l >= L&&r <= R){
		tr[p].lazy = dx;
		tr[p].sum = (r - l + 1)*dx;
		return;
	}
	pushdown(p);
	int mid = l + r >> 1;
	if(L <= mid) update(p<<1,L,R,dx);
	if(R > mid) update(p<<1|1,L,R,dx);
	pushup(p);
}
int query(int p,int L,int R){
	int l = tr[p].l,r = tr[p].r;
	if(l >= L&& r <= R) return tr[p].sum;
	pushdown(p);
	int mid = l + r >> 1;
	int ans = 0;
	if(L <= mid) ans += query(p<<1,L,R);
	if(R > mid) ans += query(p<<1|1,L,R);
	return ans;
}

bool check(int x){
	build(1,1,n);
	//将数组变成01串
	for(int i = 1;i <= n;i++){
		update(1,i,i,(a[i]>=x));
	} 
	//局部排序操作
	for(int i = 1;i <= m;i++){
		int opt = op[i].opt,L = op[i].l,R = op[i].r;
		int cnt = query(1,L,R);//这个区间1的个数
		//升序就把0全部放前面,1全部放到后面
		//降序就把1全部放前面,0全部放后面 
		if(opt == 0){
			update(1,R - cnt + 1,R,1);
			update(1,L,R - cnt,0);
		}else{
			update(1,L,L + cnt - 1,1);
			update(1,L + cnt,R,0); 
		}
	
	} 
	return query(1,q,q) == 1;
}
int main(){
	n = read();m = read();
	for(int i = 1;i <= n;i++){
		a[i] = read();
	}
	for(int i = 1;i <= m;i++){
		op[i].opt = read();op[i].l = read();op[i].r = read();
	}
	int l = 1,r = n;
	q = read();
	int ans = -1;
	while(l <= r){
		int mid = l + r >> 1;
		if(check(mid)){
			ans = mid;
			l = mid + 1;
		}else r = mid - 1;
	}
	printf("%d\n",ans);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值