首页 > 其他分享 >Luogu EI 的第六分块 // KTT 学习记录

Luogu EI 的第六分块 // KTT 学习记录

时间:2024-12-07 19:11:10浏览次数:6  
标签:tmp EI Func KTT Luogu tr dx const mx

P5693 EI 的第六分块

题目描述

给定一个整数序列,支持区间加正整数以及查询区间最大子段和。

思路

使用线段树记录四个信息来维护答案:

  • \(sum_i\):区间和;
  • \(lmax_i\):最大前缀和;
  • \(rmax_i\):最大后缀和;
  • \(mx_i\):最大子段和。

合并时我们分类讨论:

  • \(lmax = \max(lmax_{ls},sum_{ls}+lmax_{rs})\);
  • \(rmax = \max(rmax_{rs},sum_{rs}+rmax_{ls})\);
  • \(mx = \max(mx_{ls},mx_{rs},rmax_{ls}+lmax_{rs})\)。

KTT 部分

我推荐直接去看集训队论文或者一些大佬的讲解。复杂度我不会证,请看 EI 队长的证明
(KTT 我没学会严谨说明,只能全靠感性理解了。)

显然我们需要维护 \(lmax_i\)、\(rmax_i\)、\(mx_i\)、\(sum_i\) 的最大值,所以我们把需要维护的每一个信息都看作一条一次函数,现在问题变成了一次函数比大小。
我们在九年义务教育中学习一次函数时,老师教导我们要看函数的交点数形结合来求不等式解集。

Push_down/Push_up

所以我们现在额外维护一个阈值 \(dx\) 记录区间最大值何时在函数间进行交换(可以看作是两条一次函数的交点相对于区间的位置)。当 \(dx<0\) 时,代表此时区间的最大值应该在函数之间进行交换了(解集对函数的选取以交点相对于区间的位置为分界)。

区间加 \(k_i\) 看作是函数向上移动,此时我们令 \(dx\gets dx-k_i\)(可以看作是函数交点进行了左右移动)。向上维护时,我们还需要对 \(dx\) 进行更新。我们要找到最小的阈值,当然为 \(\min(dx_{ls},dx_{rs},dx\in\{x\mid f_1(x)=f_2(x)\})\)。

Rebuild

我们在 Update 后,可能会导致节点子树内的 \(dx\) 发生变化,而当 \(dx<0\) 时会导致最大值选取发生变化。
所以我们在做更新后需要对节点子树 Rebuild,二次递推来更新 \(dx\)。

Update/Query

正常进行修改和查询即可,记得 Rebuild。
(注:请注意左右区间的合并顺序对答案产生的影响。)

代码

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
template <typename T>
inline void read(T&x){//快读
	int w=0;x=0;
	char ch = getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') w=1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9'){
		x = (x<<1)+(x<<3)+(ch^48);
		ch = getchar();
	}
	if(w) x=-x;
}
template <typename T,typename...Args>
inline void read(T&t,Args&...args){
	read(t);read(args...);
}
template <typename T>
inline T Min(T x,T y){ return (x < y ? x : y); }
const int N = 4e5+10;
const ll INF = 1e16;
struct Func{//一次函数
	ll k,b;
	Func(){
		k = 0; b = 0;
	}
	Func(ll tk,ll tb){
		k = tk; b = tb;
	}
	inline Func operator + (const Func&G) const{//函数合并
		return Func(k+G.k,b+G.b);
	}
	inline bool operator < (const Func&G) const{//两个函数有没有交点
		return k==G.k && b<G.b || k<G.k;
	}
	inline ll operator & (const Func&G) const{//求交点
		return (G.b-b)/(k-G.k);
	}
	inline void operator += (const ll&G){//函数向上移动
		b += k*G;
	}
};
struct Tree{
	Func lx,rx,sum,mx; ll dx;//维护区间的四个信息以及阈值
	Tree(){
		dx = INF;//初始化为无穷大值,表示永远不会对最大值产生影响
	}
	Tree(Func tlx,Func trx,Func tsum,Func tmx,ll tdx){
		lx = tlx; rx = trx; sum = tsum; mx = tmx; dx = tdx;
	}
	inline void Merge_lx(Func x,Func y,Tree &tmp) const{//求lmax
		if(x<y) swap(x,y);
		if(x.b>=y.b) tmp.lx = x;
		else tmp.lx = y,tmp.dx = Min(tmp.dx,x&y);
	}
	inline void Merge_rx(Func x,Func y,Tree &tmp) const{//求rmax
		if(x<y) swap(x,y);
		if(x.b>=y.b) tmp.rx = x;
		else tmp.rx = y,tmp.dx = Min(tmp.dx,x&y);
	}
	inline void Merge_mx(Func x,Func y,Tree &tmp) const{//求mx
		if(x<y) swap(x,y);
		if(x.b>=y.b) tmp.mx = x;
		else tmp.mx = y,tmp.dx = Min(tmp.dx,x&y);
	}
	inline Tree operator + (const Tree&G) const{//区间合并
		Tree tmp; tmp.dx = Min(dx,G.dx); tmp.sum = sum+G.sum;
		Merge_lx(lx,sum+G.lx,tmp);Merge_rx(G.rx,G.sum+rx,tmp);
		Merge_mx(G.mx,mx,tmp);Merge_mx(tmp.mx,rx+G.lx,tmp);
		return tmp;
	}
	inline void operator += (const ll&G){//区间加
		lx += G; rx += G; mx += G; sum += G; dx -= G;
	}
}tr[N*3];
//题目说注意卡常,所以直接用zkw
//然后最优解了(雾)
int P=1,DEP=0,st[N*3]; ll tag[N*3];
inline void push_down(int p){//正常push_down
	if(tag[p]){
		tag[p<<1] += tag[p]; tr[p<<1] += tag[p];
		tag[p<<1|1] += tag[p]; tr[p<<1|1] += tag[p];
		tag[p] = 0;
	}
}
inline void rebuild(int p){
	if(tr[p].dx>=0) return ;
	int head = 1,tail = 0;
	st[++tail] = p; push_down(p);
	while(tail>=head){
		int ttail = tail;
		for(int j=tail,pos;j>=head;--j){
			pos = st[j]; //看子节点的子树是否需要更新
			if(tr[pos<<1].dx<0) st[++tail]=pos<<1,push_down(pos<<1);
			if(tr[pos<<1|1].dx<0) st[++tail]=pos<<1|1,push_down(pos<<1|1);
		}
		head = ttail+1;
	}//重新维护
	do{ tr[st[tail]]=tr[st[tail]<<1]+tr[st[tail]<<1|1]; } while(--tail); 
}
inline void update(int l,int r,ll k){
	l += P-1; r += P+1;//先push_down
	for(int dep=DEP;dep;--dep) push_down(l>>dep),push_down(r>>dep);
	while(l^1^r){
		if(~l&1) tag[l^1]+=k,tr[l^1]+=k,rebuild(l^1);//别忘了rebuild
		if(r&1) tag[r^1]+=k,tr[r^1]+=k,rebuild(r^1);
		l>>=1;r>>=1;
		tr[l] = tr[l<<1]+tr[l<<1|1];
		tr[r] = tr[r<<1]+tr[r<<1|1];
	}
	for(l>>=1; l ;l>>=1) tr[l] = tr[l<<1]+tr[l<<1|1];
}
inline ll query(int l,int r){
	l += P-1; r += P+1;//先push_down
	for(int dep=DEP;dep;--dep) push_down(l>>dep),push_down(r>>dep);
	Tree resl,resr;
	while(l^1^r){
        //注意左右区间的合并顺序
		if(~l&1) resl = resl+tr[l^1];
		if(r&1) resr = tr[r^1]+resr;
		l>>=1;r>>=1;
	}
	return (resl+resr).mx.b;
}
int main(){
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	int n,m;read(n,m);
	while(P<=n+1) P<<=1,++DEP;
	for(int i=1,k;i<=n;++i){
		read(k); Func res = Func(1ll,1ll*k);
		tr[i+P] = Tree(res,res,res,res,INF);//初始时都是y=x+a[i]
	}
	for(int i=P-1;i;--i) tr[i] = tr[i<<1]+tr[i<<1|1];
	for(int i=1,opt,l,r;i<=m;++i){
		read(opt,l,r); ll k;
		if(opt==1) read(k),update(l,r,k);
		else printf("%lld\n",(k=query(l,r))<0?0ll:k);//区间可以不选
	}
// 	fclose(stdin);
// 	fclose(stdout);
	return 0;
}

我的第一篇黑题,感谢 NianFeng 提供的帮助。

标签:tmp,EI,Func,KTT,Luogu,tr,dx,const,mx
From: https://www.cnblogs.com/Tmbcan/p/18592556

相关文章

  • 【reInvent 2024】卷炸啦,上百种模型上新至Amazon Bedrock Marketplace
    一文带你了解AmazonBedrock新功能:AmazonBedrockMarketplace文章目录一文带你了解AmazonBedrock新功能:AmazonBedrockMarketplace1️⃣AmazonBedrockMarketplace概述2️⃣AmazonBedrockMarketplace优势2.1丰富且多样的模型选择2.2统一且安全的使用体验2.3......
  • 题解:AtCoder Beginner Contest AT_abc373_d ABC373D Hidden Weights
    题目传送门题目翻译给你一个$N$个点,$M$条边的有向图,其中边有边权。现在让你给每一个点设置一个点权$a$,使得对于任意两点$x$和$y$,如果$x$到$y$有一条边,边权为$w$,那么需要满足$a_y-a_x=w$。现在让你输出一组合法的分配方案,题目保证存在,输出任意一组都行。思路1(注意......
  • 题解:[USACO07DEC] Sightseeing Cows G
    洛谷P2868题目大意有个$n$个点,$m$条边的有向图,点有点权,边有边权。现在要找出一个环,使得点权和与边权和的比值最大。思路既然说要使得点权和与边权和的比值最大,那么就会想到$01$分数规划。二分答案就不用说了,重点是这个$check$函数。$01$分数规划的板子中要检查的是......
  • 题解:AT_abc369_e [ABC369E] Sightseeing Tour
    题目大意给定一个$N$个点,$M$条边的无向图。其中边有边权。有$Q$次询问,每一次给你$K$条必须经过的边(但是方向没有限制),问从$1$到$N$的最短路长度是多少。思路观察数据范围,可以发现:虽然$M$很大,但是$N$和$K$并不大。$K\le5$,可以暴力枚举每一条边经过时的方向以及......
  • 题解:AT_abc368_d[ABC368D] Minimum Steiner Tree
    题目大意题目给定一棵由$N$个节点组成的无根树,删除其中的一些点和边,使剩下的点和边仍然能够组成一棵树,且包含给定的$K$个特殊点,问最少剩下几个点。思路我们可以发现,这棵无根树的根必须是给定的特殊点之一,不然根节点就可以删除,答案就不是最优。所以我们使用深度优先搜索遍......
  • A lightweight, ultra-fast tool for building observability pipelines
    Alightweight,ultra-fasttoolforbuildingobservabilitypipelineshttps://vector.dev/ Takecontrolofyourobservabilitydata Collect,transform,androuteallyourlogsandmetricswithonesimpletool. WhyVector?   Ultrafast......
  • Unchecked runtime.lastError: Could not establish connection. Receiving end does
    背景博客园出现如下报错,虽然没什么大碍但是看着很烦。解决查了一下是某个浏览器插件的通信问题,我解决办法也很粗暴。我进无痕,直接就没有报错了,于是开始一一排查插件。最终的罪魁祸首是这个UserAgentSwitcher。关掉后问题解决了。另一个这个是博客园广告被拦截了,于是......
  • 攻防世界-baigeiRSA2
    ⭕考察内容1、非标准RSA加密算法2、逆元求解3、因式分解(yafu)一、题目给出一串代码,大概看一眼是类似RSA的加密算法,但是其n的选取并非2个素数的乘积,而是5个素数的乘积,所以并不是标准的RSA加密。再看一眼文件,发现给出了n和e二、解题1、因数分解由于n仅有96位,所以尝试使......
  • JAVA8的computeIfAbsent使用方法
    基础说明computeIfAbsent是Java8引入的Map接口中的一个默认方法。它允许你以原子操作的方式在给定键不存在时计算其值,并将其添加到映射中。如果该键已经存在,则返回已存在的值而不执行任何计算。下面是computeIfAbsent的基本用法:Map<K,V>map=newConcurrentHashMap<......
  • 利用MeiliSearch和OpenAI API打造智能搜索系统
    利用MeiliSearch和OpenAIAPI打造智能搜索系统简介在本文中,我们将展示如何结合使用MeiliSearch和OpenAI的API来创建一个智能搜索系统。MeiliSearch是一款开源、高性能的搜索引擎,而OpenAI提供了强大的自然语言处理(NLP)模型。通过这两个工具,我们可以实现高效而智能的文本搜索......