2023NOIP A层联测9 风信子+P2048 【NOI2010】 超级钢琴 2023
P2048 【NOI2010】 超级钢琴
2023NOIP A层联测9 风信子
一年 OI 一场空,一道原题见祖宗……
Ps:超级钢琴是风信子的前置题。
超级钢琴
题意
在一段序列上,选择长度为 \(x\) 的区间且 \(x\in [L,R]\),求选择 \(k\) 个区间求和的最大值。
思路
来自洛谷第一篇 Nekroz 的题解。
将区间和变为前缀和,考虑将所有的合法方案的和拉出来排序,时间复杂度不现实,考虑贪心的解决这个问题。
设 \(S(o,l,r)=max(sum[t]-sum[o-1])|l\leq t \leq r\),即以 \(o\) 为左端点,以 \(t\) 为右端点的区间的和。\(sum\) 是前缀和,\(l,r\) 使得 \(t\) 满足题目限制。
维护 \(sum[t]\) 可以使用 \(ST\) 表 \(O(1)\) 维护。
然后考虑贪心。
我们将每次可以选择最优的 \(S(o,l,r)\),选择 \(k\) 次就是我们的结果。
初始时,是 \(n\) 个 \(i\ (i\in [1,n])\) 为起点,终点范围最大的区间,这里可以用优先队列维护(用结构体存 \(S(o,l,r)\),写构造函数比较大小)。
三元组 \(S(o,l,r)\) 选择后,会增加 \(S(o,l,t-1)\) 和 \(S(o,t+1,r)\) 两个答案区间,同时 \(S(o,l,r)\) 这个区间不能再被选中,先弹出不能选的区间,再把这两个玩意丢进堆里。对于 \(l=t\) 或 \(r=t\) 的情况需要特判。
那么这个分裂的正确性在哪里呢?
约定:下文称 \(S(o,l,r)\) 选中后分裂出的三元组为子三元组,\(S(o,l,r)\) 为父三元组。
Q:子三元组有没有可能大于父三元组,导致答案变劣。
A:不难发现,我们分裂出这个三元组的父三元组肯定大于这个三元组(因为起点相同,结束端点父三元组肯定选最大的)。所以只有父三元组被选,子三元组才会被选。
Q:父三元组选择的结束端点影响子三元组的取值,是否存在结束端点使父三元组变略,而使子三元组和父三元组共同的贡献更优。
A:每次三元组本质是一段区间,如果父三元组不选择最大段区间,肯定存在子三元组会选择最大区间,其实分到最后,每一个区间都会出现一次。其实上文的父子三元组单调性也证明了不会出现这种情况。
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e5+5;
int n,k,l,r;
int sum[maxn];
struct Tree
{
int l,r;
pair<int,int>mx={-1e9,-1e9};
}tree[maxn*10];
void build(int p,int l,int r)
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
tree[p].mx=make_pair(sum[l],l);
return ;
}
build(p*2,l,(l+r)>>1);
build(p*2+1,((l+r)>>1)+1,r);
tree[p].mx=max(tree[p*2].mx,tree[p*2+1].mx);
}
pair<int,int> query(int p,int l,int r)
{
if(tree[p].l>r||tree[p].r<l) return make_pair(-1e9,-1e9);
if(l<=tree[p].l&&tree[p].r<=r) return tree[p].mx;
return max(query(p*2,l,r),query(p*2+1,l,r));
}
struct element
{
int o,l,r,t;
friend bool operator<(element a,element b){return (sum[a.t]-sum[a.o-1])<(sum[b.t]-sum[b.o-1]);}
};
element gt(int o,int l,int r)
{
return {o,l,r,query(1,l,r).second};
}
priority_queue<element>que;
signed main()
{
scanf("%lld%lld%lld%lld",&n,&k,&l,&r);
for(int i=1;i<=n;i++)
{
int x;
scanf("%lld",&x);
sum[i]=sum[i-1]+x;
}
build(1,1,n);
for(int i=1;i+l-1<=n;i++) que.push(gt(i,i+l-1,min(i+r-1,n)));
int ans=0;
while(k--)
{
int o=que.top().o,l=que.top().l,r=que.top().r,t=que.top().t;
que.pop();
ans+=sum[t]-sum[o-1];
if(t!=l) que.push(gt(o,l,t-1));
if(t!=r) que.push(gt(o,t+1,r));
}
printf("%lld",ans);
}
风信子
题面
有两种操作
1.选择区间 \([l,r]\) 使 \(i\in [l,r]\) 中的 \(a_i\) 都加上 \(x\)。
2.在区间 \([l,r]\) 选择 \(k\) 个数对 \((i,j)\ (i\leq j)\),求 \(a_i-a_j\) 的和的最大值。
思路
\(50pts\):查询询问做一次超级钢琴,线段树区间加。
\(15pts(k=1)\):线段树维护区间答案,对于一个节点,答案可以是左右儿子的答案,也可以是左边最大-右边最小。
\(100pts\):
超级钢琴中,我们的”候补答案集合“思想是以每个点为起点做一次做三元组。
这显然不够带劲,在这里我们把起点也设成区间,那么也就是:\(S(l,r,l',r')\),其中 \([l,r]\) 是起点,\([l',r']\) 是终点。
但这样子选择区间后不方便分裂,那么我们考虑,什么样的区间分裂比较方便?
1.起点终点区间完全重合。(\(k=1\) 的做法)
2.起点终点区间没有交集。(起点取区间最大,终点去区间最小)
利用超级钢琴的思想维护优先队列即可。
这里分裂后我们的区间要满足上述两种条件,所以说最终区间如下。
维护很麻烦,但思路简单。
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ls(i) i*2
#define rs(i) i*2+1
const int maxn=1e5+5;
const int inf=1e9;
int n,m;
int a[maxn];
struct Ans
{
int val,x,y;
friend bool operator<(Ans a,Ans b){return a.val<b.val;}
friend bool operator>(Ans a,Ans b){return a.val>b.val;}
};
struct Grid
{
int val,id;
friend bool operator<(Grid a,Grid b){return a.val<b.val;}
friend bool operator>(Grid a,Grid b){return a.val>b.val;}
};
struct node1
{
int l,r,mx=-inf,mi=inf,mxid,miid,lazy;
Ans val;
}tree[maxn*10];
inline void updata(int p)
{
tree[p].val=max(tree[ls(p)].val,tree[rs(p)].val);
Ans tmp;
tmp.val=tree[ls(p)].mx-tree[rs(p)].mi;
tmp.x=tree[ls(p)].mxid,tmp.y=tree[rs(p)].miid;
tree[p].val=max(tree[p].val,tmp);
if(tree[ls(p)].mi>tree[rs(p)].mi) tree[p].mi=tree[rs(p)].mi,tree[p].miid=tree[rs(p)].miid;
else tree[p].mi=tree[ls(p)].mi,tree[p].miid=tree[ls(p)].miid;
if(tree[ls(p)].mx<tree[rs(p)].mx) tree[p].mx=tree[rs(p)].mx,tree[p].mxid=tree[rs(p)].mxid;
else tree[p].mx=tree[ls(p)].mx,tree[p].mxid=tree[ls(p)].mxid;
}
inline void push_down(int p)
{
if(tree[p].l==tree[p].r)
{
tree[p].lazy=0;
return ;
}
tree[ls(p)].lazy+=tree[p].lazy;
tree[ls(p)].mx+=tree[p].lazy;
tree[ls(p)].mi+=tree[p].lazy;
tree[rs(p)].lazy+=tree[p].lazy;
tree[rs(p)].mx+=tree[p].lazy;
tree[rs(p)].mi+=tree[p].lazy;
tree[p].lazy=0;
}
inline void build(int p,int l,int r)
{
tree[p].l=l;
tree[p].r=r;
if(l==r)
{
tree[p].mx=tree[p].mi=a[l];
tree[p].mxid=tree[p].miid=l;
tree[p].val.x=tree[p].val.y=l;
return ;
}
build(ls(p),l,l+r>>1);
build(rs(p),(l+r>>1)+1,r);
updata(p);
}
inline Grid gtmi(int p,int l,int r)
{
push_down(p);
if(tree[p].l>r||tree[p].r<l) return {inf,inf};
if(l<=tree[p].l&&tree[p].r<=r) return {tree[p].mi,tree[p].miid};
return min(gtmi(ls(p),l,r),gtmi(rs(p),l,r));
}
inline Grid gtmx(int p,int l,int r)
{
push_down(p);
if(tree[p].l>r||tree[p].r<l) return {-inf,0};
if(l<=tree[p].l&&tree[p].r<=r) return {tree[p].mx,tree[p].mxid};
return max(gtmx(ls(p),l,r),gtmx(rs(p),l,r));
}
inline Ans gtans(int p,int l,int r)
{
push_down(p);
if(tree[p].l>r||tree[p].r<l) return {-inf,0,0};
if(l<=tree[p].l&&tree[p].r<=r) return tree[p].val;
int mid=l+r>>1;
Ans t=max(gtans(ls(p),l,r),gtans(rs(p),l,r));
Grid a=gtmx(ls(p),l,r),b=gtmi(rs(p),l,r);
return max(t,(Ans){a.val-b.val,a.id,b.id});
}
inline void insert(int p,int l,int r,int val)
{
push_down(p);
if(tree[p].r<l||tree[p].l>r) return ;
if(l<=tree[p].l&&tree[p].r<=r)
{
tree[p].lazy+=val;
tree[p].mx+=val;
tree[p].mi+=val;
return ;
}
insert(ls(p),l,r,val);
insert(rs(p),l,r,val);
updata(p);
}
struct preAns
{
int al,ar,bl,br,val;
Ans Val()
{
if(ar<bl)
{
Grid a=gtmx(1,al,ar),b=gtmi(1,bl,br);
return {a.val-b.val,a.id,b.id};
}
else return gtans(1,al,ar);
}
friend bool operator<(preAns a,preAns b){return a.val<b.val;}
friend bool operator>(preAns a,preAns b){return a.val>b.val;}
};
priority_queue<preAns>que;
signed main()
{
freopen("D.in","r",stdin);
freopen("D.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,1,n);
while(m--)
{
int op,l,r,k;
scanf("%lld%lld%lld%lld",&op,&l,&r,&k);
if(op==1) insert(1,l,r,k);
else
{
preAns t;
t.al=t.bl=l,t.ar=t.br=r;
t.val=t.Val().val;
que.push(t);
int sum=0;
while(k--)
{
preAns now=que.top();
que.pop();
Ans val=now.Val();
int x=val.x,y=val.y;
sum+=now.val;
if(now.ar<now.bl)
{
if (x > now.al)
{
t=now;
t.ar=x-1;
t.val = t.Val().val;
que.push(t);
}
if (now.bl < y)
{
t=now;
t.ar=t.al=x;
t.br=y-1;
t.val = t.Val().val;
que.push(t);
}
if (y < now.br)
{
t=now;
t.ar=t.al=x;
t.bl=y+1;
t.val = t.Val().val;
que.push(t);
}
if (x < now.ar)
{
t=now;
t.al=x+1;
t.val = t.Val().val;
que.push(t);
}
}
else
{
if(x>now.al)
{
t=now;
t.ar=t.br=x-1;
t.val=t.Val().val;
que.push(t);
t=now;
t.ar=x-1,t.bl=x;
t.val=t.Val().val;
que.push(t);
}
if(x!=y)
{
t.al=t.ar=t.bl=t.br=x;
t.val=t.Val().val;
que.push(t);
}
if(x<y-1)
{
t.al=t.ar=x;
t.bl=x+1;
t.br=y-1;
t.val=t.Val().val;
que.push(t);
}
if(y<now.br)
{
t.al=t.ar=x;
t.br=now.br;
t.bl=y+1;
t.val=t.Val().val;
que.push(t);
}
if(x<now.ar)
{
t=now;
t.al=t.bl=x+1;
t.val=t.Val().val;
que.push(t);
}
}
}
printf("%lld\n",sum);
while(!que.empty()) que.pop();
}
}
}
2023NOIP A层联测9 风信子+P2048 【NOI2010】 超级钢琴 2023的更多相关文章
- P2048 [NOI2010]超级钢琴(RMQ+堆+贪心)
P2048 [NOI2010]超级钢琴 区间和--->前缀和做差 多次查询区间和最大--->前缀和RMQ 每次取出最大的区间和--->堆 于是我们设个3元组$(o,l,r)$,表示左 ...
- 洛谷 P2048 [NOI2010]超级钢琴 解题报告
P2048 [NOI2010]超级钢琴 题目描述 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号为 ...
- 【题解】P2048 [NOI2010]超级钢琴
[题解][P2048 NOI2010]超级钢琴 一道非常套路的题目.是堆的套路题. 考虑前缀和,我们要是确定了左端点,就只需要在右端区间查询最大的那个加进来就好了.\(sum_j-sum_{i-1} ...
- [洛谷P2048] [NOI2010] 超级钢琴
洛谷题目链接:[NOI2010]超级钢琴 题目描述 小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐. 这架超级钢琴可以弹奏出n个音符,编号 ...
- 洛谷 P2048 [NOI2010]超级钢琴 || Fantasy
https://www.luogu.org/problemnew/show/P2048 http://www.lydsy.com/JudgeOnline/problem.php?id=2006 首先计 ...
- Luogu P2048 [NOI2010]超级钢琴
这道题题号很清新啊!第一次开NOI的题,因为最近考到了这道题的升级版. 我们先考虑\(O(n^2)\)大暴力,就是枚举前后端点然后开一个前缀和减一下即可. 然后引入正解,我们设一个三元组\(F(s,l ...
- P2048 [NOI2010]超级钢琴 (RMQ,堆)
大意: 给定n元素序列a, 定义一个区间的权值为区间内所有元素和, 求前k大的长度在[L,R]范围内的区间的权值和. 固定右端点, 转为查询左端点最小的前缀和, 可以用RMQ O(1)查询. 要求的是 ...
- P2048 [NOI2010]超级钢琴
传送门 考虑维护前缀和 $sum[i]$ 那么对于每一个位置 $i$ ,左端点为 $i$ 右端点在 $[i+L-1,i+R-1]$ 区间的区间最大值容易维护 维护三元组 $(o,l,r)$ ,表示左端 ...
- 洛谷 P2048 [NOI2010]超级钢琴(优先队列,RMQ)
传送门 我们定义$(p,l,r)=max\{sum[t]-sum[p-1],p+l-1\leq t\leq p+r-1 \}$ 那么因为对每一个$p$来说$sum[p-1]$是一个定值,所以我们只要在 ...
- 洛谷P2048 [NOI2010]超级钢琴 题解
2019/11/14 更新日志: 近期发现这篇题解有点烂,更新一下,删繁就简,详细重点.代码多加了注释.就酱紫啦! 正解步骤 我们需要先算美妙度的前缀和,并初始化RMQ. 循环 \(i\) 从 \(1 ...
随机推荐
- ASP.NET Core 如何紀錄 Entity Framework Core 5.0 自動產生的 SQL 命令
在最近的幾個 Entity Framework Core 版本,對於 Logging (紀錄) 的撰寫方式一直在改變,大致上可區分成 EF Core 2.1 , EF Core 3.0+ 與 EF C ...
- SQL Server使用脚本实现自动备份
因服务器安装的SQL Server版本不支持自动定时备份,需自行实现,大概思路为: 创建备份数据库的脚本 创建批处理脚本执行步骤一中的脚本 创建Windows定时任务执行步骤二中的脚本 1. 创建SQ ...
- springCloud allibaba 微服务引言
微服务篇: springcloud 常见组件有哪些 nacos 的服务注册表结构是怎样的 nacos 如何支撑阿里内部数十万服务注册压力 nacos 如何避免并发读写冲突问题 nacos 和eurek ...
- 单个48TB大小SQL Server数据库备份导致日志文件无法截断
单个48TB大小SQL Server数据库备份导致日志文件无法截断 SQL Server 版本:SQL Server 2019背景在一个48T大小的单数据库环境中,采用简单恢复模式,日志文件大小限制为 ...
- 【转】git常用操作
创建版本库 git clone url 克隆远程版本库 git init 初始化本地版本库 配置 git config --global user.name 'chengcp' 配置global级别的 ...
- RxJS 系列 – Conditional and Boolean Operators
前言 前几篇介绍过了 Creation Operators Filtering Operators Join Creation Operators Error Handling Operators T ...
- 邀请你参与字节跳动 UME 插件开发竞赛
UME 是由字节跳动 Flutter Infra 团队出品和维护的 Flutter 应用内调试工具.通过在 Flutter 应用中加入 UME 工具,开发者们可以直接在应用内查看调试信息,而无需使用 ...
- 线段树 ----洛谷p3372
问题描述: 已知一个数列,对数列进行两种操作:1,对数列某个区间中的所有数加d:2,查询数列某区间的区间和 输入: 第一行输入两个整数n和m,分别代表数列中元素个数和对数列的操作次数,第二行输入n个用 ...
- 3. 无重复字符的最长子串 Golang实现
题目描述 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度. 注意区分子串和子序列. 示例 3: 输入: s = "pwwkew" 输出: 3 解释: 因为无重复 ...
- Vscode 远程切换Python虚拟环境
在VSCode中远程切换Python虚拟环境是一个涉及多个步骤的过程,包括安装必要的扩展.连接到远程服务器.创建或激活虚拟环境,并在VSCode中选择相应的Python解释器.以下是一个详细的步骤指南 ...