当前位置: 首页 > news >正文

题解:P14638 [NOIP2025] 序列询问

前言

本文同步自洛谷专栏,题目传送门。

参考资料:题解,作者 Doqe 大佬。

做法:具体地按 \(L\) 分块解决 \(R<2\times L\) 的情况。

当然,也可以认为我是来提供代码实现的。

分析

特殊情况

设当前询问范围为 \([L,R]\),采用按 \(L\) 分块解决 \(R<2\times L\) 的情况。

首先构建两个 ST 表,分别支持前缀和数组区间最小值、最大值。

假设当前的分块为 \([l,r]=[l,l+L)\),那么,所有与此块有交的区间可以分为三种(有重合,但不影响答案):

  • 左端点在块内。

  • 右端点在块内。

  • 包含整块。

右端点在块内时,枚举块内的点作为右端点,利用 ST 表得出各点的答案(没有可行区间就设置为负无穷)。

然后可以发现:由于块长为 \(L\)\(x\) 作为右端点时,所得的答案对 \([l,x]\) 都成立,可以进行一个后缀 \(\max\) 完成统计,复杂度 \(O(L)\)

左端点在块内时同理,注意前缀和数组的一些下标问题,统计用前缀 \(\max\)

包含整块的情况,暴力向右枚举右端点,并规定左端点 \(\le l\),则长度一定 \(\ge L\),一直枚举到不存在 \(\le R\) 的情况为止,复杂度 \(O(R-L)=O(L)\)

全局共 \(\frac{n}{L}\) 块,总复杂度就是 \(O(n)\)

倍增预处理解决一般情况

按照值域倍增,预处理出 \([2^i,2^{i+1})\) 的答案,然后合并为区间,时间复杂度和空间复杂度都是 \(O(n\log^2{n})\)

注:也可以再建一个 ST 表,但是复杂度上没有必要。

然后每次询问时,将询问拆成三部分:\([L,2^x),[2^x,2^y),[2^y,R]\)

其中 \(2^{x-1}\le L<2^x,2^y\le R<2^{y+1}\)

可以解决此问题,时间复杂度 \(O(nq)\),空间 \(O(n\log^2{n})\)

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define gc() getchar()
#define ll long long
#define N 50005
#define inf -1000000000000000ll
#define ull unsigned long longint n,q,u;
ll ans[N],w[N],mx[20][20][N];
ll maxn[20][N],minn[20][N],res[N];
ull tot;inline int read(){int a=0,c=gc(),f=0;for(;!isdigit(c);c=gc()) f|=c=='-';for(;isdigit(c);c=gc()) a=10*a+c-'0';return f?-a:a;
}inline void cmax(ll &x,ll y){(x<y)&&(x=y);}
inline void cmin(ll &x,ll y){(x>y)&&(x=y);}
inline int LG(int x){return 31^__builtin_clz(x);}inline ll get_mi(int l,int r){int d=LG(r-l+1);return min(minn[d][l],minn[d][r-(1<<d)+1]);
}inline ll get_mx(int l,int r){int d=LG(r-l+1);return max(maxn[d][l],maxn[d][r-(1<<d)+1]);
}void calc(int L,int R){//每次处理一个长为 l 的区间,答案为前后缀 maxfor(int l=1,r=L;l<=r;l+=L,r=min(r+L,n)){for(int i=l;i<=r;i++){w[i]=i-L>=0?minn[0][i]-get_mi(max(0,i-R),i-L):inf;}for(int i=r-1;i>=l;i--) cmax(w[i],w[i+1]);for(int i=l;i<=r;i++) ans[i]=w[i];//赋值,可以不用写清空//分割线,上面在枚举右端点。下面的部分中,注意枚举左端点时,前缀和数组下标 -1for(int i=r;i>=l;i--){w[i]=i+L-1<=n?get_mx(i+L-1,min(n,i+R-1))-maxn[0][i-1]:inf;}for(int i=l+1;i<=r;i++) cmax(w[i],w[i-1]);for(int i=l;i<=r;i++) cmax(ans[i],w[i]);//统计包含这个块的答案ll ret=inf;for(int i=r+1;i<=n;i++){if(i-R>l-1) break;cmax(ret,minn[0][i]-get_mi(max(i-R,0),l-1));}for(int i=l;i<=r;i++) cmax(ans[i],ret);}
}int main(){// freopen("query.in","r",stdin);// freopen("query.out","w",stdout);n=read(),u=LG(n);for(int i=1;i<=n;i++){maxn[0][i]=minn[0][i]=read()+maxn[0][i-1];}for(int i=1;i<=LG(n+1);i++){for(int j=0,up=n-(1<<i)+1;j<=up;j++){maxn[i][j]=maxn[i-1][j],cmax(maxn[i][j],maxn[i-1][j+(1<<i-1)]);}}for(int i=1;i<=LG(n+1);i++){for(int j=0,up=n-(1<<i)+1;j<=up;j++){minn[i][j]=minn[i-1][j],cmin(minn[i][j],minn[i-1][j+(1<<i-1)]);}}for(int i=0;i<=u;i++){for(int j=0;j<=u;j++){for(int k=1;k<=n;k++) mx[i][j][k]=inf;}}for(int i=0;i<u;i++){calc(1<<i,(1<<i+1)-1);for(int j=1;j<=n;j++) mx[i][i][j]=ans[j];}for(int l=0;l<u;l++){//直接处理每个区间for(int r=l+1;r<u;r++){for(int i=1;i<=n;i++){mx[l][r][i]=mx[l][r-1][i];cmax(mx[l][r][i],mx[r][r][i]);}  }}q=read();for(int id=1,l,r;id<=q;id++){l=read(),r=read(),tot=0;if(r<2*l){calc(l,r);for(int i=1;i<=n;i++) tot^=(ull)(i*ans[i]);printf("%llu\n",tot);continue;}int x=LG(l)+1,y=LG(r);for(int i=1;i<=n;i++) res[i]=mx[x][y-1][i];calc(l,(1<<x)-1);for(int i=1;i<=n;i++) cmax(res[i],ans[i]);calc(1<<y,r);for(int i=1;i<=n;i++) cmax(res[i],ans[i]);for(int i=1;i<=n;i++) tot^=(ull)(i*res[i]);printf("%llu\n",tot);}return 0;
}

无需预处理的方法

为参考资料中的 bonus。

可以将询问简单拆为两部分:\([L,\frac{R}{2}],[\frac{R+1}{2},R]\)

\([\frac{R+1}{2},R]\) 为特殊情况,容易解决。

\([L,\frac{R}{2}]\) 若应用特殊情况的算法,求解包含整块的情况时复杂度错误。

考虑特殊处理,可以利用这样一个特点:

对于一个块 \([l,r]\),若用 \([l+L-1,l+R-1]\) 中的最大值减去 \([r-R,r-L]\) 中的最小值,所得结果一定包括长度为 \([L,R]\) 的情况,

但是会错误地求解长度为 \(l+R-1-(r-R)=2\times R-(r-l+1)\le 2\times R-1\)

$\red{\text{注}}$

\(r-l+1\) 未必是 \(L\),因为在最后一块处情况特殊。(代码实现时也要注意!)


但是在此处却不影响整体答案,问题得以解决。

时间复杂度 \(O(nq)\),空间 \(O(n\log{n})\)

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define gc() getchar()
#define ll long long
#define N 50005
#define inf -1000000000000000ll
#define ull unsigned long longint n,q;
ll ans[N],w[N],maxn[20][N],minn[20][N];
ull tot;inline int read(){int a=0,c=gc(),f=0;for(;!isdigit(c);c=gc()) f|=c=='-';for(;isdigit(c);c=gc()) a=10*a+c-'0';return f?-a:a;
}inline void cmax(ll &x,ll y){(x<y)&&(x=y);}
inline void cmin(ll &x,ll y){(x>y)&&(x=y);}
inline int LG(int x){return 31^__builtin_clz(x);}
inline ll pmax(ll x,ll y){return x<y?y:x;}
inline ll pmin(ll x,ll y){return x>y?y:x;}inline ll get_mi(int l,int r){int d=LG(r-l+1);return pmin(minn[d][l],minn[d][r-(1<<d)+1]);
}inline ll get_mx(int l,int r){int d=LG(r-l+1);return pmax(maxn[d][l],maxn[d][r-(1<<d)+1]);
}void calc(int L,int R,int o){//每次处理一个长为 l 的区间,答案为前后缀 max,R<=2Lfor(int l=1,r=L;l<=r;l+=L,r=min(r+L,n)){for(int i=l;i<=r;i++){w[i]=i-L>=0?minn[0][i]-get_mi(max(0,i-R),i-L):inf;}for(int i=r-1;i>=l;i--) cmax(w[i],w[i+1]);for(int i=l;i<=r;i++) cmax(ans[i],w[i]);//分割线,上面在枚举右端点。下面的部分中,注意枚举左端点时,前缀和数组下标 -1for(int i=r;i>=l;i--){w[i]=i+L-1<=n?get_mx(i+L-1,min(n,i+R-1))-maxn[0][i-1]:inf;}for(int i=l+1;i<=r;i++) cmax(w[i],w[i-1]);for(int i=l;i<=r;i++) cmax(ans[i],w[i]);}if(o==0) return;for(int l=1,r=L;l<=r;l+=L,r=min(r+L,n)){//统计包含这个块的答案ll ret=inf;for(int i=r+1;i<=n;i++){//<=n 的限制要加入if(i-R>l-1) break;cmax(ret,minn[0][i]-get_mi(max(i-R,0),l-1));}for(int i=l;i<=r;i++) cmax(ans[i],ret);}
}void solve(int L,int R){//此处 R*2 <= 原来 Rfor(int l=1,r=L;l<=r;l+=L,r=min(r+L,n)){//特别注意最后一块可能长度不足,r-L 不能写 l-1ll ret=get_mx(r,min(n,l+R-1))-get_mi(max(0,r-R),r-L);for(int i=l;i<=r;i++) cmax(ans[i],ret);}
}void init(){for(int i=1;i<=n;i++){maxn[0][i]=minn[0][i]=read()+maxn[0][i-1];}for(int i=1;i<=LG(n+1);i++){for(int j=0,up=n-(1<<i)+1;j<=up;j++){maxn[i][j]=pmax(maxn[i-1][j],maxn[i-1][j+(1<<i-1)]);}}for(int i=1;i<=LG(n+1);i++){for(int j=0,up=n-(1<<i)+1;j<=up;j++){minn[i][j]=pmin(minn[i-1][j],minn[i-1][j+(1<<i-1)]);}}
}int main(){// freopen("query.in","r",stdin);// freopen("query.out","w",stdout);n=read(),init(),q=read();for(int id=1,l,r;id<=q;id++){l=read(),r=read(),tot=0;for(int i=1;i<=n;i++) ans[i]=inf;calc(max(l,(r+1)>>1),r,1);if(l<((r+1)>>1)) calc(l,r>>1,0),solve(l,r>>1);for(int i=1;i<=n;i++) tot^=(ull)(i*ans[i]);printf("%llu\n",tot);}return 0;
}

后话

说一下蒟蒻赛时的一个 \(O(nq\log{nq})\) 的暴力(然而写挂了)。

把所有询问离线,\(L,R\) 放在一起后再塞入形如 \(2^i\) 的数,排序,每一段分别求解。

此时发现正着扫一遍(枚举左端点)和反着扫一遍(枚举右端点)可以更新到所有点,使用 zkw 线段树维护。

大抵是接近 \(R\le 2\times L\) 的特殊情况,但太弱了没有发现可以归结出来,也想不到按 \(L\) 分块,只能 \(O(n\log{n})\) 做。

赛时花了两个小时写这个做法,还写挂了……一分暴力都没有……

赛时算的空间 \(nq\) 恰好够,赛后想起来有个二倍常数……

警示后人,先写最基础的暴力(本来想写个高分暴力来着)。

听闻机房同学 T4 拼出了 45pts 的高分,膜。

http://www.zskr.cn/news/1466130.html

相关文章:

  • QMCDecode终极指南:五分钟解锁QQ音乐加密音频
  • 企业级私有化LLM平台实战指南:构建安全可控的智能知识管理系统
  • 别再截图了!用Cadence自带工具导出论文级原理图,清晰度提升600%
  • 多标签分类与主题建模在科学文献分类中的应用
  • 材料表面瑕疵识别实战代码包:Python+OpenCV全流程实现,含测试图与可视化流程图
  • 2026年洛阳婚礼堂全案设计与宴会厅改造一站式落地完全指南 - 企业名录优选推荐
  • 基于复杂巨系统闭环演化范式的意识涌现机制研究——兼论六大主流意识理论的范式局限性
  • 告别8字节限制:在STM32H7上实战CAN FD,实现64字节数据帧收发
  • 从写代码到连节点:老Shader程序员转用ShaderGraph的避坑指南与效率对比
  • 聚合型AI平台选型指南:五大工程维度深度解析
  • 2026年洛阳婚礼堂全案设计与宴会酒店升级改造深度指南:一站式落地方案对标解析 - 企业名录优选推荐
  • 2026年陕西乳品企业包装服务商选择指南:五大关键维度解析与推荐 - 2026年企业资讯
  • MuleSoft企业级AI编排:LLM生产落地的稳定性与治理实践
  • 如何轻松抓取网页视频?猫抓浏览器扩展的5大实用技巧
  • 2026 石家庄创业经营者一致认可正规财税公司哪家好?石家庄高性价比财税机构推荐:代理记账、公司注册代办权威口碑排名 - 品牌智鉴榜
  • 别再只盯着PS的GPIO了!手把手教你用Vivado配置AXI GPIO软核(附中断配置避坑指南)
  • 神经科学如何重塑AI工程实践:从突触可塑性到类脑计算落地
  • 2026六月依据实时金价测评:广州黄金回收优质门店排名 - 奢侈品交易观察员
  • Python基础:Python命名规范与命名习惯全掌握
  • Poetry 依赖管理实战:从 pip 迁移的工程化升级
  • 武汉名包回收“内幕”:高价靠谱的渠道藏在这里,别再被坑 - 奢侈品交易观察员
  • 大润发购物卡余额别浪费!零钱到账完整操作步骤 - 团团收购物卡回收
  • JetBrains IDE试用期重置终极指南:一键恢复30天免费使用
  • 实战应用,基于快马ai定制wsl环境,快速部署ubuntu下的web开发项目
  • 2026年广州餐饮点餐小程序多少钱 - 凡科杰建云
  • 2026年路径规划API对比:丰图/高德/百度/腾讯哪家强?实测避坑指南
  • 破解传统鼠控痛点:景隆3S智能鼠饵站方法论如何重构虫控效率? - 资讯纵览
  • 告别龟速下载!保姆级教程:Windows 10/11下用迅雷搞定Qt 5.14.2离线安装包
  • 2026年|降AI收藏!学长实测10款AI智能降重工具红黑榜:论文降AI避坑(含免费降低AI率办法) - 降AI小能手
  • 广州到泰国跨境物流专线公司排行榜7项重要热门问题解答:深度测评广州华鹰国际进出口有限公司 - 资讯纵览